From 2e99210dfe6481974b75f6d562028915ea10dda8 Mon Sep 17 00:00:00 2001 From: vasavk Date: Wed, 24 Apr 2024 18:06:21 +0530 Subject: [PATCH 01/46] Digital ACT-191 ONEAPP-7016 story: added new page --- VDS.xcodeproj/project.pbxproj | 12 ++++++ VDS/Components/Calendar/Calendar.swift | 56 ++++++++++++++++++++++++++ VDS/VDS.docc/VDS.md | 1 + 3 files changed, 69 insertions(+) create mode 100644 VDS/Components/Calendar/Calendar.swift diff --git a/VDS.xcodeproj/project.pbxproj b/VDS.xcodeproj/project.pbxproj index f805c10f..35f61ed9 100644 --- a/VDS.xcodeproj/project.pbxproj +++ b/VDS.xcodeproj/project.pbxproj @@ -16,6 +16,7 @@ 186D13CB2BBA8B1500986B53 /* DropdownSelect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 186D13CA2BBA8B1500986B53 /* DropdownSelect.swift */; }; 186D13CF2BBC36EF00986B53 /* DropdownSelectChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 186D13CE2BBC36EE00986B53 /* DropdownSelectChangeLog.txt */; }; 18792A902B7431F2008C0D29 /* ButtonIconBadgeIndicatorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18792A8F2B7431F2008C0D29 /* ButtonIconBadgeIndicatorModel.swift */; }; + 18A3F12A2BD9298900498E4A /* Calendar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18A3F1292BD9298900498E4A /* Calendar.swift */; }; 18A65A022B96E848006602CC /* Breadcrumbs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18A65A012B96E848006602CC /* Breadcrumbs.swift */; }; 18A65A042B96F050006602CC /* BreadcrumbItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18A65A032B96F050006602CC /* BreadcrumbItem.swift */; }; 18B463A42BBD3C46005C4528 /* DropdownOptionModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18B463A32BBD3C46005C4528 /* DropdownOptionModel.swift */; }; @@ -205,6 +206,7 @@ 186D13CA2BBA8B1500986B53 /* DropdownSelect.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DropdownSelect.swift; sourceTree = ""; }; 186D13CE2BBC36EE00986B53 /* DropdownSelectChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = DropdownSelectChangeLog.txt; sourceTree = ""; }; 18792A8F2B7431F2008C0D29 /* ButtonIconBadgeIndicatorModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonIconBadgeIndicatorModel.swift; sourceTree = ""; }; + 18A3F1292BD9298900498E4A /* Calendar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Calendar.swift; sourceTree = ""; }; 18A65A012B96E848006602CC /* Breadcrumbs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Breadcrumbs.swift; sourceTree = ""; }; 18A65A032B96F050006602CC /* BreadcrumbItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BreadcrumbItem.swift; sourceTree = ""; }; 18B463A32BBD3C46005C4528 /* DropdownOptionModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DropdownOptionModel.swift; sourceTree = ""; }; @@ -415,6 +417,14 @@ path = DropdownSelect; sourceTree = ""; }; + 18A3F1202BD8F5DE00498E4A /* Calendar */ = { + isa = PBXGroup; + children = ( + 18A3F1292BD9298900498E4A /* Calendar.swift */, + ); + path = Calendar; + sourceTree = ""; + }; 18A65A002B96E7E1006602CC /* Breadcrumbs */ = { isa = PBXGroup; children = ( @@ -573,6 +583,7 @@ EAD062AE2A3B87210015965D /* BadgeIndicator */, 18A65A002B96E7E1006602CC /* Breadcrumbs */, EA0FC2BE2912D18200DF80B4 /* Buttons */, + 18A3F1202BD8F5DE00498E4A /* Calendar */, 1808BEBA2BA41B1D00129230 /* CarouselScrollbar */, EAF7F092289985E200B287F5 /* Checkbox */, 186D13C92BBA8A3500986B53 /* DropdownSelect */, @@ -1106,6 +1117,7 @@ EAB2376229E9880400AABE9A /* TrailingTooltipLabel.swift in Sources */, EAACB8982B92706F006A3869 /* DefaultValuing.swift in Sources */, EAB2376A29E9E59100AABE9A /* TooltipLaunchable.swift in Sources */, + 18A3F12A2BD9298900498E4A /* Calendar.swift in Sources */, 18A65A042B96F050006602CC /* BreadcrumbItem.swift in Sources */, EAB2375D29E8789100AABE9A /* Tooltip.swift in Sources */, 71BFA70A2B7F70E6000DCE33 /* DropShadowable.swift in Sources */, diff --git a/VDS/Components/Calendar/Calendar.swift b/VDS/Components/Calendar/Calendar.swift new file mode 100644 index 00000000..b6aa9ecf --- /dev/null +++ b/VDS/Components/Calendar/Calendar.swift @@ -0,0 +1,56 @@ +// +// Calendar.swift +// VDS +// +// Created by Kanamarlapudi, Vasavi on 19/04/24. +// + +import Foundation +import UIKit +import VDSTokens +import Combine + +/// A calendar is a monthly view that lets customers select a single date. +@objc(VDSCalendar) +open class CalendarBase: View { + + //-------------------------------------------------- + // 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) + } + + //-------------------------------------------------- + // MARK: - Private Properties + //-------------------------------------------------- + internal var containerSize: CGSize { CGSize(width: 290, height: 300) } + + internal var containerView = View().with { + $0.clipsToBounds = true + } + + //-------------------------------------------------- + // MARK: - Lifecycle + //-------------------------------------------------- + open override func initialSetup() { + super.initialSetup() + } + + open override func setup() { + super.setup() + isAccessibilityElement = false + } + + open override func reset() { + super.reset() + } +} diff --git a/VDS/VDS.docc/VDS.md b/VDS/VDS.docc/VDS.md index 607e09f5..d145ea0a 100755 --- a/VDS/VDS.docc/VDS.md +++ b/VDS/VDS.docc/VDS.md @@ -25,6 +25,7 @@ Using the system allows designers and developers to collaborate more easily and - ``Button`` - ``ButtonIcon`` - ``ButtonGroup`` +- ``CalendarBase`` - ``CarouselScrollbar`` - ``Checkbox`` - ``CheckboxItem`` From 440f516522551cac8d1942cfc627db5e17cb16a5 Mon Sep 17 00:00:00 2001 From: vasavk Date: Fri, 26 Apr 2024 17:02:18 +0530 Subject: [PATCH 02/46] Digital ACT-191 ONEAPP-7016 story: changes for calendar base view with header and footer. --- VDS.xcodeproj/project.pbxproj | 8 ++ VDS/Components/Calendar/Calendar.swift | 100 +++++++++++++++++- .../CalendarDateCollectionViewCell.swift | 54 ++++++++++ .../Calendar/CalendarHeaderReusableView.swift | 57 ++++++++++ 4 files changed, 218 insertions(+), 1 deletion(-) create mode 100644 VDS/Components/Calendar/CalendarDateCollectionViewCell.swift create mode 100644 VDS/Components/Calendar/CalendarHeaderReusableView.swift diff --git a/VDS.xcodeproj/project.pbxproj b/VDS.xcodeproj/project.pbxproj index 35f61ed9..5a2e0c91 100644 --- a/VDS.xcodeproj/project.pbxproj +++ b/VDS.xcodeproj/project.pbxproj @@ -17,6 +17,8 @@ 186D13CF2BBC36EF00986B53 /* DropdownSelectChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 186D13CE2BBC36EE00986B53 /* DropdownSelectChangeLog.txt */; }; 18792A902B7431F2008C0D29 /* ButtonIconBadgeIndicatorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18792A8F2B7431F2008C0D29 /* ButtonIconBadgeIndicatorModel.swift */; }; 18A3F12A2BD9298900498E4A /* Calendar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18A3F1292BD9298900498E4A /* Calendar.swift */; }; + 18A3F1322BD944E800498E4A /* CalendarDateCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18A3F1312BD944E800498E4A /* CalendarDateCollectionViewCell.swift */; }; + 18A3F1382BDA693000498E4A /* CalendarHeaderReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18A3F1372BDA693000498E4A /* CalendarHeaderReusableView.swift */; }; 18A65A022B96E848006602CC /* Breadcrumbs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18A65A012B96E848006602CC /* Breadcrumbs.swift */; }; 18A65A042B96F050006602CC /* BreadcrumbItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18A65A032B96F050006602CC /* BreadcrumbItem.swift */; }; 18B463A42BBD3C46005C4528 /* DropdownOptionModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18B463A32BBD3C46005C4528 /* DropdownOptionModel.swift */; }; @@ -207,6 +209,8 @@ 186D13CE2BBC36EE00986B53 /* DropdownSelectChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = DropdownSelectChangeLog.txt; sourceTree = ""; }; 18792A8F2B7431F2008C0D29 /* ButtonIconBadgeIndicatorModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonIconBadgeIndicatorModel.swift; sourceTree = ""; }; 18A3F1292BD9298900498E4A /* Calendar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Calendar.swift; sourceTree = ""; }; + 18A3F1312BD944E800498E4A /* CalendarDateCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarDateCollectionViewCell.swift; sourceTree = ""; }; + 18A3F1372BDA693000498E4A /* CalendarHeaderReusableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarHeaderReusableView.swift; sourceTree = ""; }; 18A65A012B96E848006602CC /* Breadcrumbs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Breadcrumbs.swift; sourceTree = ""; }; 18A65A032B96F050006602CC /* BreadcrumbItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BreadcrumbItem.swift; sourceTree = ""; }; 18B463A32BBD3C46005C4528 /* DropdownOptionModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DropdownOptionModel.swift; sourceTree = ""; }; @@ -421,6 +425,8 @@ isa = PBXGroup; children = ( 18A3F1292BD9298900498E4A /* Calendar.swift */, + 18A3F1312BD944E800498E4A /* CalendarDateCollectionViewCell.swift */, + 18A3F1372BDA693000498E4A /* CalendarHeaderReusableView.swift */, ); path = Calendar; sourceTree = ""; @@ -1144,6 +1150,7 @@ 71ACE89C2BA0451200FB6ADC /* PaginationContainer.swift in Sources */, EAC71A1F2A2E173D00E47A9F /* RadioButton.swift in Sources */, EA33622C2891E73B0071C351 /* FontProtocol.swift in Sources */, + 18A3F1322BD944E800498E4A /* CalendarDateCollectionViewCell.swift in Sources */, EA596ABD2A16B4EC00300C4B /* Tab.swift in Sources */, 71ACE89E2BA1CC1700FB6ADC /* TiletEyebrowModel.swift in Sources */, EAF7F11728A1475A00B287F5 /* RadioButtonItem.swift in Sources */, @@ -1153,6 +1160,7 @@ 71FC86DC2B96F4C800700965 /* PaginationCellItem.swift in Sources */, EAC846F3294B95CE00F685BA /* ButtonGroupCollectionViewCell.swift in Sources */, EAF7F0952899861000B287F5 /* CheckboxItem.swift in Sources */, + 18A3F1382BDA693000498E4A /* CalendarHeaderReusableView.swift in Sources */, EA985BE82968951C00F2FF2E /* TileletTitleModel.swift in Sources */, 71FC86DE2B9738B900700965 /* SurfaceConfigurationValue.swift in Sources */, EA297A5529FB07760031ED56 /* TooltipLabelAttribute.swift in Sources */, diff --git a/VDS/Components/Calendar/Calendar.swift b/VDS/Components/Calendar/Calendar.swift index b6aa9ecf..641aee5d 100644 --- a/VDS/Components/Calendar/Calendar.swift +++ b/VDS/Components/Calendar/Calendar.swift @@ -32,12 +32,35 @@ open class CalendarBase: View { //-------------------------------------------------- // MARK: - Private Properties //-------------------------------------------------- - internal var containerSize: CGSize { CGSize(width: 290, height: 300) } + internal var containerSize: CGSize { CGSize(width: 320, height: 376) } //width:320/328 + private let cellItemSize = CGSize(width: 40, height: 40) + private let headerHeight = 104.0 + private let footerHeight = 56.0 + private let items = 35 internal var containerView = View().with { $0.clipsToBounds = true } + ///Collectionview to render Breadcrumb Items + private lazy var collectionView: UICollectionView = { + let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout()) + collectionView.isScrollEnabled = false + collectionView.translatesAutoresizingMaskIntoConstraints = false + collectionView.delegate = self + collectionView.dataSource = self + collectionView.showsHorizontalScrollIndicator = false + collectionView.showsVerticalScrollIndicator = false + collectionView.register(CalendarDateCollectionViewCell.self, forCellWithReuseIdentifier: CalendarDateCollectionViewCell.identifier) + collectionView.register(CalendarHeaderReusableView.self, + forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, + withReuseIdentifier: CalendarHeaderReusableView.identifier) + collectionView.register(CalendarFooterReusableView.self, + forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter, + withReuseIdentifier: CalendarFooterReusableView.identifier) + return collectionView + }() + //-------------------------------------------------- // MARK: - Lifecycle //-------------------------------------------------- @@ -48,9 +71,84 @@ open class CalendarBase: View { open override func setup() { super.setup() isAccessibilityElement = false + + addSubview(containerView) + containerView + .pinTop() + .pinBottom() + .pinLeadingGreaterThanOrEqualTo() + .pinTrailingLessThanOrEqualTo() + .height(containerSize.height) + .width(containerSize.width) + + containerView.centerXAnchor.constraint(equalTo: centerXAnchor).activate() + + // Calendar View + containerView.addSubview(collectionView) + collectionView.pinToSuperView() } + override open func layoutSubviews() { + super.layoutSubviews() + } + open override func reset() { super.reset() } } + +extension CalendarBase: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { + //-------------------------------------------------- + // MARK: - UICollectionView Delegate & Datasource + //-------------------------------------------------- + public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + items + } + + public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CalendarDateCollectionViewCell.identifier, for: indexPath) as? CalendarDateCollectionViewCell else { return UICollectionViewCell() } + return cell + } + + public func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { + if kind == UICollectionView.elementKindSectionHeader { + // Header + guard let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: CalendarHeaderReusableView.identifier, for: indexPath) as? CalendarHeaderReusableView else { + return UICollectionReusableView() + } + header.configure(with: true) + return header + } else { + // Footer + if kind == UICollectionView.elementKindSectionFooter { + guard let footer = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: CalendarFooterReusableView.identifier, for: indexPath) as? CalendarFooterReusableView else { + return UICollectionReusableView() + } + footer.configure(with: true) + return footer + } + } + return UICollectionReusableView() + } + + public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize { + return CGSize(width: collectionView.frame.size.width, height: headerHeight) + } + + public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize { + return CGSize(width: collectionView.frame.size.width, height: footerHeight) + } + + public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat { + return VDSLayout.space1X + } + + public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat { + return VDSLayout.space1X + } + + public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + return cellItemSize + } + +} diff --git a/VDS/Components/Calendar/CalendarDateCollectionViewCell.swift b/VDS/Components/Calendar/CalendarDateCollectionViewCell.swift new file mode 100644 index 00000000..156cce04 --- /dev/null +++ b/VDS/Components/Calendar/CalendarDateCollectionViewCell.swift @@ -0,0 +1,54 @@ +// +// CalendarDateCollectionViewCell.swift +// VDS +// +// Created by Kanamarlapudi, Vasavi on 24/04/24. +// + +import Foundation +import UIKit +import VDSTokens + +///This is customised view for Calendar cell item +final class CalendarDateCollectionViewCell: UICollectionViewCell { + + ///Identifier for the Calendar Date Cell + static let identifier: String = String(describing: CalendarDateCollectionViewCell.self) + + //-------------------------------------------------- + // MARK: - Private Properties + //-------------------------------------------------- +// internal var stackView: UIStackView = { +// return UIStackView().with { +// $0.translatesAutoresizingMaskIntoConstraints = false +// $0.axis = .horizontal +// $0.distribution = .fill +// $0.alignment = .center +// $0.spacing = VDSLayout.space2X +// $0.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal) +// $0.setContentHuggingPriority(.defaultHigh, for: .horizontal) +// } +// }() +// +// private lazy var selectionBackgroundView = View().with { +// $0.translatesAutoresizingMaskIntoConstraints = false +// $0.clipsToBounds = true +// $0.backgroundColor = .systemRed +// } +// +// private lazy var numberLabel = Label().with { +// $0.translatesAutoresizingMaskIntoConstraints = false +// $0.textAlignment = .center +// // $0.font +// // $0.textColor +// } + + override init(frame:CGRect) { + super.init(frame: frame) + contentView.backgroundColor = .link + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/VDS/Components/Calendar/CalendarHeaderReusableView.swift b/VDS/Components/Calendar/CalendarHeaderReusableView.swift new file mode 100644 index 00000000..3f139a10 --- /dev/null +++ b/VDS/Components/Calendar/CalendarHeaderReusableView.swift @@ -0,0 +1,57 @@ +// +// CalendarHeaderReusableView.swift +// VDS +// +// Created by Kanamarlapudi, Vasavi on 24/04/24. +// + +import UIKit + +/// Custom header view +class CalendarHeaderReusableView: UICollectionReusableView { + + ///Identifier for the Calendar Header Reusable View + static let identifier: String = String(describing: CalendarHeaderReusableView.self) + + override init(frame: CGRect) { + super.init(frame: frame) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func configure(with color: Bool) { + // Make a view and make in generic and dynamic + self.backgroundColor = .orange + } + + override func layoutSubviews() { + super.layoutSubviews() + } +} + +/// Custom footer view +class CalendarFooterReusableView: UICollectionReusableView { + + ///Identifier for the Calendar Footer Reusable View + static let identifier: String = String(describing: CalendarFooterReusableView.self) + + override init(frame: CGRect) { + super.init(frame: frame) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func configure(with color: Bool) { + // Make a view and make in generic and dynamic + self.backgroundColor = .green + } + + override func layoutSubviews() { + super.layoutSubviews() + } +} + From 2b16fecdc353020b3dadc998913042413072c1e4 Mon Sep 17 00:00:00 2001 From: vasavk Date: Mon, 29 Apr 2024 10:10:26 +0530 Subject: [PATCH 03/46] Digital ACT-191 ONEAPP-7016 story: added calendar indicator model --- VDS.xcodeproj/project.pbxproj | 4 +++ .../Calendar/CalendarIndicatorModel.swift | 25 +++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 VDS/Components/Calendar/CalendarIndicatorModel.swift diff --git a/VDS.xcodeproj/project.pbxproj b/VDS.xcodeproj/project.pbxproj index 5a2e0c91..1890cb34 100644 --- a/VDS.xcodeproj/project.pbxproj +++ b/VDS.xcodeproj/project.pbxproj @@ -23,6 +23,7 @@ 18A65A042B96F050006602CC /* BreadcrumbItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18A65A032B96F050006602CC /* BreadcrumbItem.swift */; }; 18B463A42BBD3C46005C4528 /* DropdownOptionModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18B463A32BBD3C46005C4528 /* DropdownOptionModel.swift */; }; 18BDEE822B75316E00452358 /* ButtonIconChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 18BDEE812B75316E00452358 /* ButtonIconChangeLog.txt */; }; + 18FEA1AD2BDD137500A56439 /* CalendarIndicatorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18FEA1AC2BDD137500A56439 /* CalendarIndicatorModel.swift */; }; 445BA07829C07B3D0036A7C5 /* Notification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 445BA07729C07B3D0036A7C5 /* Notification.swift */; }; 44604AD429CE186A00E62B51 /* NotificationButtonModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44604AD329CE186A00E62B51 /* NotificationButtonModel.swift */; }; 44604AD729CE196600E62B51 /* Line.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44604AD629CE196600E62B51 /* Line.swift */; }; @@ -215,6 +216,7 @@ 18A65A032B96F050006602CC /* BreadcrumbItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BreadcrumbItem.swift; sourceTree = ""; }; 18B463A32BBD3C46005C4528 /* DropdownOptionModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DropdownOptionModel.swift; sourceTree = ""; }; 18BDEE812B75316E00452358 /* ButtonIconChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = ButtonIconChangeLog.txt; sourceTree = ""; }; + 18FEA1AC2BDD137500A56439 /* CalendarIndicatorModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarIndicatorModel.swift; sourceTree = ""; }; 445BA07729C07B3D0036A7C5 /* Notification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notification.swift; sourceTree = ""; }; 44604AD329CE186A00E62B51 /* NotificationButtonModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationButtonModel.swift; sourceTree = ""; }; 44604AD629CE196600E62B51 /* Line.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Line.swift; sourceTree = ""; }; @@ -427,6 +429,7 @@ 18A3F1292BD9298900498E4A /* Calendar.swift */, 18A3F1312BD944E800498E4A /* CalendarDateCollectionViewCell.swift */, 18A3F1372BDA693000498E4A /* CalendarHeaderReusableView.swift */, + 18FEA1AC2BDD137500A56439 /* CalendarIndicatorModel.swift */, ); path = Calendar; sourceTree = ""; @@ -1247,6 +1250,7 @@ EA596ABF2A16B4F500300C4B /* Tabs.swift in Sources */, EAD062A72A3B67770015965D /* UIView+CALayer.swift in Sources */, EAD068942A560C13002E3A2D /* LoaderLaunchable.swift in Sources */, + 18FEA1AD2BDD137500A56439 /* CalendarIndicatorModel.swift in Sources */, EA985BEC2968A91200F2FF2E /* TitleLockupTitleModel.swift in Sources */, 5FC35BE328D51405004EBEAC /* Button.swift in Sources */, ); diff --git a/VDS/Components/Calendar/CalendarIndicatorModel.swift b/VDS/Components/Calendar/CalendarIndicatorModel.swift new file mode 100644 index 00000000..04ad9714 --- /dev/null +++ b/VDS/Components/Calendar/CalendarIndicatorModel.swift @@ -0,0 +1,25 @@ +// +// CalendarIndicatorModel.swift +// VDS +// +// Created by Kanamarlapudi, Vasavi on 26/04/24. +// + +import Foundation + +/// Custom data type for indicators prop +extension CalendarBase { + public struct CalendarIndicatorModel { + + /// Text that shown to an indicator for legend + public var label: String + + /// Date to an indicator + public var date: Date + + public init(label: String, date: Date) { + self.label = label + self.date = date + } + } +} From 58ceee5d47f8f9f006609a51b1e6a3410679cddf Mon Sep 17 00:00:00 2001 From: vasavk Date: Mon, 29 Apr 2024 10:13:58 +0530 Subject: [PATCH 04/46] Digital ACT-191 ONEAPP-7016 story: added properties --- VDS/Components/Calendar/Calendar.swift | 58 +++++++++++++++++++++----- 1 file changed, 48 insertions(+), 10 deletions(-) diff --git a/VDS/Components/Calendar/Calendar.swift b/VDS/Components/Calendar/Calendar.swift index 641aee5d..cb938dde 100644 --- a/VDS/Components/Calendar/Calendar.swift +++ b/VDS/Components/Calendar/Calendar.swift @@ -29,6 +29,45 @@ open class CalendarBase: View { super.init(coder: coder) } + //-------------------------------------------------- + // MARK: - Public Properties + //-------------------------------------------------- + /// If set to true, the calendar will not have a border. + open var hideContainerBorder: Bool = false + + /// If set to true, the calendar will not have current date indication. + open var hideCurrentDateIndicator: Bool = false + + /// Enable specific days. Pass an array of string value in date format e.g. ['07/21/2024', '07/24/2024', 07/28/2024']. + /// All other dates will be inactive + open var activeDates: [Date] = [] + + /// Disable specific days. Pass an array of string value in date format e.g. ['07/21/2024', '07/24/2024', 07/28/2024']. + /// All other dates will be active. + open var inactiveDates: [Date] = [] + + /// If provided, the calendar will allow a selection to be made from this date forward. Defaults to today. + open var minDate: Date? + + /// If provided, the calendar will allow a selection to be made up to this date. + open var maxDate: Date? + + /// If provided, this is the date that will show as selected by the Calendar. + /// If no value is provided, the current date will be used. If null is provided, no date will be selected. + open var selectedDate: Date? + + /// If provided, the calendar will be rendered with transparent background. + open var transparentBackground: Bool = false + + /// Array of ``CalendarIndicatorModel`` you are wanting to show on legend. + open var indicators: [CalendarIndicatorModel] = [] { didSet { setNeedsUpdate() } } + + /// Array of indicators for the legend. + open var indicatorData: [CalendarIndicatorModel] = [] { didSet { setNeedsUpdate() } } + + /// A callback when the selected date changes.. + open var onChangeSelectedDate: ((Date) -> String)? + //-------------------------------------------------- // MARK: - Private Properties //-------------------------------------------------- @@ -37,12 +76,12 @@ open class CalendarBase: View { private let headerHeight = 104.0 private let footerHeight = 56.0 private let items = 35 - + internal var containerView = View().with { $0.clipsToBounds = true } - ///Collectionview to render Breadcrumb Items + /// Collectionview to load calendar month view private lazy var collectionView: UICollectionView = { let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout()) collectionView.isScrollEnabled = false @@ -53,11 +92,11 @@ open class CalendarBase: View { collectionView.showsVerticalScrollIndicator = false collectionView.register(CalendarDateCollectionViewCell.self, forCellWithReuseIdentifier: CalendarDateCollectionViewCell.identifier) collectionView.register(CalendarHeaderReusableView.self, - forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, - withReuseIdentifier: CalendarHeaderReusableView.identifier) + forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, + withReuseIdentifier: CalendarHeaderReusableView.identifier) collectionView.register(CalendarFooterReusableView.self, - forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter, - withReuseIdentifier: CalendarFooterReusableView.identifier) + forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter, + withReuseIdentifier: CalendarFooterReusableView.identifier) return collectionView }() @@ -85,9 +124,9 @@ open class CalendarBase: View { // Calendar View containerView.addSubview(collectionView) - collectionView.pinToSuperView() + collectionView.pinToSuperView() } - + override open func layoutSubviews() { super.layoutSubviews() } @@ -146,9 +185,8 @@ extension CalendarBase: UICollectionViewDelegate, UICollectionViewDataSource, UI public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat { return VDSLayout.space1X } - + public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { return cellItemSize } - } From 7438d65fe53d2a46959c26be258eab2945cbf346 Mon Sep 17 00:00:00 2001 From: vasavk Date: Tue, 30 Apr 2024 11:17:14 +0530 Subject: [PATCH 05/46] Digital ACT-191 ONEAPP-7016 story: updating footer with indicators data --- VDS.xcodeproj/project.pbxproj | 12 +- VDS/Components/Calendar/Calendar.swift | 13 +- .../Calendar/CalendarIndicatorModel.swift | 4 +- .../Calendar/CalendarLegendView.swift | 204 ++++++++++++++++++ ...eView.swift => CalendarReusableView.swift} | 13 +- 5 files changed, 230 insertions(+), 16 deletions(-) create mode 100644 VDS/Components/Calendar/CalendarLegendView.swift rename VDS/Components/Calendar/{CalendarHeaderReusableView.swift => CalendarReusableView.swift} (83%) diff --git a/VDS.xcodeproj/project.pbxproj b/VDS.xcodeproj/project.pbxproj index 1890cb34..354969af 100644 --- a/VDS.xcodeproj/project.pbxproj +++ b/VDS.xcodeproj/project.pbxproj @@ -18,12 +18,13 @@ 18792A902B7431F2008C0D29 /* ButtonIconBadgeIndicatorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18792A8F2B7431F2008C0D29 /* ButtonIconBadgeIndicatorModel.swift */; }; 18A3F12A2BD9298900498E4A /* Calendar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18A3F1292BD9298900498E4A /* Calendar.swift */; }; 18A3F1322BD944E800498E4A /* CalendarDateCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18A3F1312BD944E800498E4A /* CalendarDateCollectionViewCell.swift */; }; - 18A3F1382BDA693000498E4A /* CalendarHeaderReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18A3F1372BDA693000498E4A /* CalendarHeaderReusableView.swift */; }; 18A65A022B96E848006602CC /* Breadcrumbs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18A65A012B96E848006602CC /* Breadcrumbs.swift */; }; 18A65A042B96F050006602CC /* BreadcrumbItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18A65A032B96F050006602CC /* BreadcrumbItem.swift */; }; 18B463A42BBD3C46005C4528 /* DropdownOptionModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18B463A32BBD3C46005C4528 /* DropdownOptionModel.swift */; }; 18BDEE822B75316E00452358 /* ButtonIconChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 18BDEE812B75316E00452358 /* ButtonIconChangeLog.txt */; }; 18FEA1AD2BDD137500A56439 /* CalendarIndicatorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18FEA1AC2BDD137500A56439 /* CalendarIndicatorModel.swift */; }; + 18FEA1B12BE0B69300A56439 /* CalendarLegendView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18FEA1B02BE0B69300A56439 /* CalendarLegendView.swift */; }; + 18FEA1B32BE0BC8700A56439 /* CalendarReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18FEA1B22BE0BC8700A56439 /* CalendarReusableView.swift */; }; 445BA07829C07B3D0036A7C5 /* Notification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 445BA07729C07B3D0036A7C5 /* Notification.swift */; }; 44604AD429CE186A00E62B51 /* NotificationButtonModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44604AD329CE186A00E62B51 /* NotificationButtonModel.swift */; }; 44604AD729CE196600E62B51 /* Line.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44604AD629CE196600E62B51 /* Line.swift */; }; @@ -211,12 +212,13 @@ 18792A8F2B7431F2008C0D29 /* ButtonIconBadgeIndicatorModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonIconBadgeIndicatorModel.swift; sourceTree = ""; }; 18A3F1292BD9298900498E4A /* Calendar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Calendar.swift; sourceTree = ""; }; 18A3F1312BD944E800498E4A /* CalendarDateCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarDateCollectionViewCell.swift; sourceTree = ""; }; - 18A3F1372BDA693000498E4A /* CalendarHeaderReusableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarHeaderReusableView.swift; sourceTree = ""; }; 18A65A012B96E848006602CC /* Breadcrumbs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Breadcrumbs.swift; sourceTree = ""; }; 18A65A032B96F050006602CC /* BreadcrumbItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BreadcrumbItem.swift; sourceTree = ""; }; 18B463A32BBD3C46005C4528 /* DropdownOptionModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DropdownOptionModel.swift; sourceTree = ""; }; 18BDEE812B75316E00452358 /* ButtonIconChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = ButtonIconChangeLog.txt; sourceTree = ""; }; 18FEA1AC2BDD137500A56439 /* CalendarIndicatorModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarIndicatorModel.swift; sourceTree = ""; }; + 18FEA1B02BE0B69300A56439 /* CalendarLegendView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarLegendView.swift; sourceTree = ""; }; + 18FEA1B22BE0BC8700A56439 /* CalendarReusableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarReusableView.swift; sourceTree = ""; }; 445BA07729C07B3D0036A7C5 /* Notification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notification.swift; sourceTree = ""; }; 44604AD329CE186A00E62B51 /* NotificationButtonModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationButtonModel.swift; sourceTree = ""; }; 44604AD629CE196600E62B51 /* Line.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Line.swift; sourceTree = ""; }; @@ -428,8 +430,9 @@ children = ( 18A3F1292BD9298900498E4A /* Calendar.swift */, 18A3F1312BD944E800498E4A /* CalendarDateCollectionViewCell.swift */, - 18A3F1372BDA693000498E4A /* CalendarHeaderReusableView.swift */, 18FEA1AC2BDD137500A56439 /* CalendarIndicatorModel.swift */, + 18FEA1B02BE0B69300A56439 /* CalendarLegendView.swift */, + 18FEA1B22BE0BC8700A56439 /* CalendarReusableView.swift */, ); path = Calendar; sourceTree = ""; @@ -1163,7 +1166,6 @@ 71FC86DC2B96F4C800700965 /* PaginationCellItem.swift in Sources */, EAC846F3294B95CE00F685BA /* ButtonGroupCollectionViewCell.swift in Sources */, EAF7F0952899861000B287F5 /* CheckboxItem.swift in Sources */, - 18A3F1382BDA693000498E4A /* CalendarHeaderReusableView.swift in Sources */, EA985BE82968951C00F2FF2E /* TileletTitleModel.swift in Sources */, 71FC86DE2B9738B900700965 /* SurfaceConfigurationValue.swift in Sources */, EA297A5529FB07760031ED56 /* TooltipLabelAttribute.swift in Sources */, @@ -1216,7 +1218,9 @@ EA0B180A2AA78F9000F2D0CD /* UIEdgeInsets.swift in Sources */, EA985C1D296CD13600F2FF2E /* BundleManager.swift in Sources */, EA0B18052A9E2D2D00F2D0CD /* SelectorBase.swift in Sources */, + 18FEA1B12BE0B69300A56439 /* CalendarLegendView.swift in Sources */, EAC71A1D2A2E155A00E47A9F /* Checkbox.swift in Sources */, + 18FEA1B32BE0BC8700A56439 /* CalendarReusableView.swift in Sources */, EAF7F0AB289B13FD00B287F5 /* TextStyleLabelAttribute.swift in Sources */, EAB1D29C28A5618900DAE764 /* RadioButtonGroup.swift in Sources */, EA81410B2A0E8E3C004F60D2 /* ButtonIcon.swift in Sources */, diff --git a/VDS/Components/Calendar/Calendar.swift b/VDS/Components/Calendar/Calendar.swift index cb938dde..c9312c4e 100644 --- a/VDS/Components/Calendar/Calendar.swift +++ b/VDS/Components/Calendar/Calendar.swift @@ -62,8 +62,8 @@ open class CalendarBase: View { /// Array of ``CalendarIndicatorModel`` you are wanting to show on legend. open var indicators: [CalendarIndicatorModel] = [] { didSet { setNeedsUpdate() } } - /// Array of indicators for the legend. - open var indicatorData: [CalendarIndicatorModel] = [] { didSet { setNeedsUpdate() } } +// /// Array of indicators for the legend. +// open var indicatorData: [CalendarIndicatorModel] = [] { didSet { setNeedsUpdate() } } /// A callback when the selected date changes.. open var onChangeSelectedDate: ((Date) -> String)? @@ -76,7 +76,7 @@ open class CalendarBase: View { private let headerHeight = 104.0 private let footerHeight = 56.0 private let items = 35 - + internal var containerView = View().with { $0.clipsToBounds = true } @@ -127,6 +127,11 @@ open class CalendarBase: View { collectionView.pinToSuperView() } + open override func updateView() { + super.updateView() + collectionView.reloadData() + } + override open func layoutSubviews() { super.layoutSubviews() } @@ -163,7 +168,7 @@ extension CalendarBase: UICollectionViewDelegate, UICollectionViewDataSource, UI guard let footer = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: CalendarFooterReusableView.identifier, for: indexPath) as? CalendarFooterReusableView else { return UICollectionReusableView() } - footer.configure(with: true) + footer.configure(with: indicators) return footer } } diff --git a/VDS/Components/Calendar/CalendarIndicatorModel.swift b/VDS/Components/Calendar/CalendarIndicatorModel.swift index 04ad9714..911afe36 100644 --- a/VDS/Components/Calendar/CalendarIndicatorModel.swift +++ b/VDS/Components/Calendar/CalendarIndicatorModel.swift @@ -8,7 +8,7 @@ import Foundation /// Custom data type for indicators prop -extension CalendarBase { +//extension CalendarBase { public struct CalendarIndicatorModel { /// Text that shown to an indicator for legend @@ -22,4 +22,4 @@ extension CalendarBase { self.date = date } } -} +//} diff --git a/VDS/Components/Calendar/CalendarLegendView.swift b/VDS/Components/Calendar/CalendarLegendView.swift new file mode 100644 index 00000000..df1bbdf6 --- /dev/null +++ b/VDS/Components/Calendar/CalendarLegendView.swift @@ -0,0 +1,204 @@ +// +// CalendarLegendView.swift +// VDS +// +// Created by Kanamarlapudi, Vasavi on 29/04/24. +// + +import Foundation +import UIKit +import VDSTokens +import Combine + +/// Legend view to show array of indicators as calendar footer view. +open class CalendarLegendView: View { + //-------------------------------------------------- + // 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) + } + + //-------------------------------------------------- + // MARK: - Public Properties + //-------------------------------------------------- + open var items: [CalendarIndicatorModel] = [] { didSet { setNeedsUpdate() } } + + //-------------------------------------------------- + // MARK: - Private Properties + //-------------------------------------------------- + internal var containerSize: CGSize { CGSize(width: 320, height: 56) } //width:320/328 + + internal var containerView = View().with { + $0.clipsToBounds = true + } + + private let flowLayout = UICollectionViewFlowLayout().with { + $0.estimatedItemSize = UICollectionViewFlowLayout.automaticSize + $0.minimumLineSpacing = VDSLayout.space1X + $0.minimumInteritemSpacing = VDSLayout.space4X + $0.scrollDirection = .vertical + } + open lazy var legendCollectionView = UICollectionView(frame: .zero, collectionViewLayout: flowLayout).with { + $0.isScrollEnabled = false + $0.translatesAutoresizingMaskIntoConstraints = false + $0.showsVerticalScrollIndicator = false + $0.showsHorizontalScrollIndicator = false + $0.isAccessibilityElement = true + $0.backgroundColor = .clear + $0.delegate = self + $0.dataSource = self + $0.register(LegendCollectionViewCell.self, forCellWithReuseIdentifier: LegendCollectionViewCell.identifier) + } + + //-------------------------------------------------- + // MARK: - Lifecycle + //-------------------------------------------------- + open override func initialSetup() { + super.initialSetup() + } + + open override func setup() { + super.setup() + isAccessibilityElement = false + + addSubview(containerView) + containerView + .pinTop(VDSLayout.space6X) + .pinBottom(VDSLayout.space4X) + .pinLeadingGreaterThanOrEqualTo(leadingAnchor, VDSLayout.space5X, .defaultLow) + .pinTrailingLessThanOrEqualTo(trailingAnchor, VDSLayout.space5X, .defaultHigh) + .height(containerSize.height) + .width(containerSize.width) + + containerView.centerXAnchor.constraint(equalTo: centerXAnchor).activate() + + // legend Collection View + containerView.addSubview(legendCollectionView) + legendCollectionView.pinToSuperView() + + } + + open override func updateView() { + super.updateView() + legendCollectionView.reloadData() + } + + override open func layoutSubviews() { + super.layoutSubviews() + } + + open override func reset() { + super.reset() + } +} + +extension CalendarLegendView: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { + + public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return items.count + } + + public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + guard collectionView == legendCollectionView, + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: LegendCollectionViewCell.identifier, for: indexPath) as? LegendCollectionViewCell, + indexPath.row <= items.count else { return UICollectionViewCell() } + let text = items[indexPath.row].label + cell.updateTitle(text: text, color: VDSColor.elementsSecondaryOnlight, surface: surface, clearFullcircle: indexPath.row == 1, drawSemiCircle: indexPath.row == 2) + return cell + } +} + +private class LegendCollectionViewCell: UICollectionViewCell { + + static let identifier: String = String(describing: LegendCollectionViewCell.self) + + private let textColorConfiguration = SurfaceColorConfiguration(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark) + + private let indicatorColorConfiguration = SurfaceColorConfiguration(VDSColor.elementsSecondaryOnlight, VDSColor.elementsSecondaryOndark) + + private var title = Label().with { + $0.translatesAutoresizingMaskIntoConstraints = false + $0.textAlignment = .left + $0.numberOfLines = 1 + $0.textStyle = .bodySmall + $0.backgroundColor = .clear + $0.isAccessibilityElement = false + } + private var legendIndicatorWrapper: View = View().with { + $0.translatesAutoresizingMaskIntoConstraints = false + $0.backgroundColor = .clear + } + private var legendIndicator: View = View().with { + $0.translatesAutoresizingMaskIntoConstraints = false + $0.backgroundColor = .clear + $0.layer.borderWidth = 1.0 + } + + private lazy var stackView = UIStackView().with { + $0.translatesAutoresizingMaskIntoConstraints = false + $0.distribution = .equalSpacing + $0.spacing = VDSLayout.space2X + $0.axis = .horizontal + $0.backgroundColor = .clear + } + + private lazy var shapeLayer = CAShapeLayer() + + //-------------------------------------------------- + // MARK: - Initializers + //-------------------------------------------------- + override init(frame: CGRect) { + super.init(frame: frame) + setupCell() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + setupCell() + } + + func setupCell() { + addSubview(stackView) + stackView.pinToSuperView() + + legendIndicatorWrapper.addSubview(legendIndicator) + legendIndicator.pinLeading().pinTrailing().width(8).height(8).pinCenterY() + + stackView.addArrangedSubview(legendIndicatorWrapper) + stackView.addArrangedSubview(title) + } + + func updateTitle(text: String, color: UIColor, surface: Surface, clearFullcircle: Bool, drawSemiCircle: Bool) { + title.text = text + title.textColor = textColorConfiguration.getColor(surface) + + legendIndicator.backgroundColor = drawSemiCircle ? .clear : (clearFullcircle ? .clear : color) + legendIndicator.layer.borderColor = indicatorColorConfiguration.getColor(surface).cgColor + + self.layoutIfNeeded() + + legendIndicator.layer.cornerRadius = legendIndicator.frame.size.height / 2.0 + + guard drawSemiCircle else { return } + + let center = CGPoint(x: legendIndicator.frame.size.width/2, y: legendIndicator.frame.size.height/2) + let path = UIBezierPath() + path.move(to: center) + path.addArc(withCenter: center, radius: center.x, startAngle: 2 * .pi, endAngle: .pi, clockwise: true) + path.close() + shapeLayer.path = path.cgPath + shapeLayer.fillColor = color.cgColor + + guard legendIndicator.layer.sublayers?.contains(shapeLayer) ?? true else { return } + legendIndicator.layer.addSublayer(shapeLayer) + } +} diff --git a/VDS/Components/Calendar/CalendarHeaderReusableView.swift b/VDS/Components/Calendar/CalendarReusableView.swift similarity index 83% rename from VDS/Components/Calendar/CalendarHeaderReusableView.swift rename to VDS/Components/Calendar/CalendarReusableView.swift index 3f139a10..8131ce33 100644 --- a/VDS/Components/Calendar/CalendarHeaderReusableView.swift +++ b/VDS/Components/Calendar/CalendarReusableView.swift @@ -1,11 +1,12 @@ // -// CalendarHeaderReusableView.swift +// CalendarReusableView.swift // VDS // // Created by Kanamarlapudi, Vasavi on 24/04/24. // import UIKit +import VDSTokens /// Custom header view class CalendarHeaderReusableView: UICollectionReusableView { @@ -23,7 +24,6 @@ class CalendarHeaderReusableView: UICollectionReusableView { func configure(with color: Bool) { // Make a view and make in generic and dynamic - self.backgroundColor = .orange } override func layoutSubviews() { @@ -37,6 +37,8 @@ class CalendarFooterReusableView: UICollectionReusableView { ///Identifier for the Calendar Footer Reusable View static let identifier: String = String(describing: CalendarFooterReusableView.self) + private lazy var footerView = CalendarLegendView() + override init(frame: CGRect) { super.init(frame: frame) } @@ -45,13 +47,12 @@ class CalendarFooterReusableView: UICollectionReusableView { fatalError("init(coder:) has not been implemented") } - func configure(with color: Bool) { - // Make a view and make in generic and dynamic - self.backgroundColor = .green + func configure(with indicators: [CalendarIndicatorModel]) { + footerView.items = indicators + addSubview(footerView) } override func layoutSubviews() { super.layoutSubviews() } } - From ef80db9e73c22888208b3c607784c364abc86175 Mon Sep 17 00:00:00 2001 From: vasavk Date: Tue, 30 Apr 2024 14:21:43 +0530 Subject: [PATCH 06/46] Digital ACT-191 ONEAPP-7016 story: minor changes --- VDS/Components/Calendar/Calendar.swift | 1 + VDS/Components/Calendar/CalendarIndicatorModel.swift | 4 ++-- VDS/Components/Calendar/CalendarLegendView.swift | 4 +++- VDS/Components/Calendar/CalendarReusableView.swift | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/VDS/Components/Calendar/Calendar.swift b/VDS/Components/Calendar/Calendar.swift index c9312c4e..a7ed560a 100644 --- a/VDS/Components/Calendar/Calendar.swift +++ b/VDS/Components/Calendar/Calendar.swift @@ -90,6 +90,7 @@ open class CalendarBase: View { collectionView.dataSource = self collectionView.showsHorizontalScrollIndicator = false collectionView.showsVerticalScrollIndicator = false + collectionView.backgroundColor = .clear collectionView.register(CalendarDateCollectionViewCell.self, forCellWithReuseIdentifier: CalendarDateCollectionViewCell.identifier) collectionView.register(CalendarHeaderReusableView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, diff --git a/VDS/Components/Calendar/CalendarIndicatorModel.swift b/VDS/Components/Calendar/CalendarIndicatorModel.swift index 911afe36..04ad9714 100644 --- a/VDS/Components/Calendar/CalendarIndicatorModel.swift +++ b/VDS/Components/Calendar/CalendarIndicatorModel.swift @@ -8,7 +8,7 @@ import Foundation /// Custom data type for indicators prop -//extension CalendarBase { +extension CalendarBase { public struct CalendarIndicatorModel { /// Text that shown to an indicator for legend @@ -22,4 +22,4 @@ import Foundation self.date = date } } -//} +} diff --git a/VDS/Components/Calendar/CalendarLegendView.swift b/VDS/Components/Calendar/CalendarLegendView.swift index df1bbdf6..78826ffd 100644 --- a/VDS/Components/Calendar/CalendarLegendView.swift +++ b/VDS/Components/Calendar/CalendarLegendView.swift @@ -30,7 +30,7 @@ open class CalendarLegendView: View { //-------------------------------------------------- // MARK: - Public Properties //-------------------------------------------------- - open var items: [CalendarIndicatorModel] = [] { didSet { setNeedsUpdate() } } + open var items: [CalendarBase.CalendarIndicatorModel] = [] { didSet { setNeedsUpdate() } } //-------------------------------------------------- // MARK: - Private Properties @@ -90,6 +90,8 @@ open class CalendarLegendView: View { open override func updateView() { super.updateView() legendCollectionView.reloadData() + setNeedsLayout() + layoutIfNeeded() } override open func layoutSubviews() { diff --git a/VDS/Components/Calendar/CalendarReusableView.swift b/VDS/Components/Calendar/CalendarReusableView.swift index 8131ce33..96995006 100644 --- a/VDS/Components/Calendar/CalendarReusableView.swift +++ b/VDS/Components/Calendar/CalendarReusableView.swift @@ -47,7 +47,7 @@ class CalendarFooterReusableView: UICollectionReusableView { fatalError("init(coder:) has not been implemented") } - func configure(with indicators: [CalendarIndicatorModel]) { + func configure(with indicators: [CalendarBase.CalendarIndicatorModel]) { footerView.items = indicators addSubview(footerView) } From 23831589f0dba211193205d47b51a43424f0e6be Mon Sep 17 00:00:00 2001 From: vasavk Date: Tue, 30 Apr 2024 19:19:28 +0530 Subject: [PATCH 07/46] Digital ACT-191 ONEAPP-7016 story: added header view and updated with week days --- VDS.xcodeproj/project.pbxproj | 8 + VDS/Components/Calendar/Calendar.swift | 13 +- .../Calendar/CalendarHeaderView.swift | 195 ++++++++++++++++++ .../Calendar/CalendarReusableView.swift | 4 +- VDS/Components/Calendar/Date+Extension.swift | 20 ++ 5 files changed, 238 insertions(+), 2 deletions(-) create mode 100644 VDS/Components/Calendar/CalendarHeaderView.swift create mode 100644 VDS/Components/Calendar/Date+Extension.swift diff --git a/VDS.xcodeproj/project.pbxproj b/VDS.xcodeproj/project.pbxproj index 514e3fa0..33835770 100644 --- a/VDS.xcodeproj/project.pbxproj +++ b/VDS.xcodeproj/project.pbxproj @@ -25,6 +25,8 @@ 18FEA1AD2BDD137500A56439 /* CalendarIndicatorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18FEA1AC2BDD137500A56439 /* CalendarIndicatorModel.swift */; }; 18FEA1B12BE0B69300A56439 /* CalendarLegendView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18FEA1B02BE0B69300A56439 /* CalendarLegendView.swift */; }; 18FEA1B32BE0BC8700A56439 /* CalendarReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18FEA1B22BE0BC8700A56439 /* CalendarReusableView.swift */; }; + 18FEA1B52BE0E63600A56439 /* Date+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18FEA1B42BE0E63600A56439 /* Date+Extension.swift */; }; + 18FEA1B72BE0EBFE00A56439 /* CalendarHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18FEA1B62BE0EBFE00A56439 /* CalendarHeaderView.swift */; }; 445BA07829C07B3D0036A7C5 /* Notification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 445BA07729C07B3D0036A7C5 /* Notification.swift */; }; 44604AD429CE186A00E62B51 /* NotificationButtonModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44604AD329CE186A00E62B51 /* NotificationButtonModel.swift */; }; 44604AD729CE196600E62B51 /* Line.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44604AD629CE196600E62B51 /* Line.swift */; }; @@ -219,6 +221,8 @@ 18FEA1AC2BDD137500A56439 /* CalendarIndicatorModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarIndicatorModel.swift; sourceTree = ""; }; 18FEA1B02BE0B69300A56439 /* CalendarLegendView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarLegendView.swift; sourceTree = ""; }; 18FEA1B22BE0BC8700A56439 /* CalendarReusableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarReusableView.swift; sourceTree = ""; }; + 18FEA1B42BE0E63600A56439 /* Date+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+Extension.swift"; sourceTree = ""; }; + 18FEA1B62BE0EBFE00A56439 /* CalendarHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarHeaderView.swift; sourceTree = ""; }; 445BA07729C07B3D0036A7C5 /* Notification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notification.swift; sourceTree = ""; }; 44604AD329CE186A00E62B51 /* NotificationButtonModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationButtonModel.swift; sourceTree = ""; }; 44604AD629CE196600E62B51 /* Line.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Line.swift; sourceTree = ""; }; @@ -433,6 +437,8 @@ 18FEA1AC2BDD137500A56439 /* CalendarIndicatorModel.swift */, 18FEA1B02BE0B69300A56439 /* CalendarLegendView.swift */, 18FEA1B22BE0BC8700A56439 /* CalendarReusableView.swift */, + 18FEA1B42BE0E63600A56439 /* Date+Extension.swift */, + 18FEA1B62BE0EBFE00A56439 /* CalendarHeaderView.swift */, ); path = Calendar; sourceTree = ""; @@ -1170,6 +1176,7 @@ 71FC86DE2B9738B900700965 /* SurfaceConfigurationValue.swift in Sources */, EA297A5529FB07760031ED56 /* TooltipLabelAttribute.swift in Sources */, EA985BEA29689B6D00F2FF2E /* TileletSubTitleModel.swift in Sources */, + 18FEA1B72BE0EBFE00A56439 /* CalendarHeaderView.swift in Sources */, EA3361C9289054C50071C351 /* Surfaceable.swift in Sources */, EAB5FEED2927E1B200998C17 /* ButtonGroupPositionLayout.swift in Sources */, EA4DB30228DCBCA500103EE3 /* Badge.swift in Sources */, @@ -1239,6 +1246,7 @@ 5F21D7BF28DCEB3D003E7CD6 /* Useable.swift in Sources */, EAF7F0B7289C12A600B287F5 /* UITapGestureRecognizer.swift in Sources */, EA0D1C392A6AD4DF00E5C127 /* Typography+SpacingConfig.swift in Sources */, + 18FEA1B52BE0E63600A56439 /* Date+Extension.swift in Sources */, EAB2376629E9952D00AABE9A /* UIApplication.swift in Sources */, EAB5FED429267EB300998C17 /* UIView+NSLayoutConstraint.swift in Sources */, EAB2376829E9992800AABE9A /* TooltipAlertViewController.swift in Sources */, diff --git a/VDS/Components/Calendar/Calendar.swift b/VDS/Components/Calendar/Calendar.swift index a7ed560a..0700bb55 100644 --- a/VDS/Components/Calendar/Calendar.swift +++ b/VDS/Components/Calendar/Calendar.swift @@ -101,6 +101,15 @@ open class CalendarBase: View { return collectionView }() + //-------------------------------------------------- + // MARK: - Configuration + //-------------------------------------------------- + internal var containerBorderColorConfiguration = SurfaceColorConfiguration(VDSColor.elementsPrimaryOnlight , VDSColor.elementsPrimaryOndark) + internal var backgroundColorConfiguration = ControlColorConfiguration().with { + $0.setSurfaceColors(VDSFormControlsColor.backgroundOnlight, VDSFormControlsColor.backgroundOndark, forState: .normal) + $0.setSurfaceColors(VDSFormControlsColor.backgroundOnlight, VDSFormControlsColor.backgroundOndark, forState: .disabled) + } + //-------------------------------------------------- // MARK: - Lifecycle //-------------------------------------------------- @@ -122,7 +131,8 @@ open class CalendarBase: View { .width(containerSize.width) containerView.centerXAnchor.constraint(equalTo: centerXAnchor).activate() - + containerView.layer.borderWidth = VDSFormControls.borderWidth + // Calendar View containerView.addSubview(collectionView) collectionView.pinToSuperView() @@ -131,6 +141,7 @@ open class CalendarBase: View { open override func updateView() { super.updateView() collectionView.reloadData() + containerView.layer.borderColor = containerBorderColorConfiguration.getColor(self).cgColor } override open func layoutSubviews() { diff --git a/VDS/Components/Calendar/CalendarHeaderView.swift b/VDS/Components/Calendar/CalendarHeaderView.swift new file mode 100644 index 00000000..2e591b94 --- /dev/null +++ b/VDS/Components/Calendar/CalendarHeaderView.swift @@ -0,0 +1,195 @@ +// +// CalendarHeaderView.swift +// VDS +// +// Created by Kanamarlapudi, Vasavi on 30/04/24. +// + +import Foundation +import UIKit +import VDSTokens + +/// Header view to display month and year along with days of week +open class CalendarHeaderView: View { + //-------------------------------------------------- + // 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) + } + + //-------------------------------------------------- + // MARK: - Public Properties + //-------------------------------------------------- + + //-------------------------------------------------- + // MARK: - Private Properties + //-------------------------------------------------- + internal var containerSize: CGSize { CGSize(width: 320, height: 104) } //width:320/328 + + private lazy var stackView = UIStackView().with { + $0.translatesAutoresizingMaskIntoConstraints = false + $0.distribution = .fill + $0.spacing = VDSLayout.space1X + $0.axis = .vertical + $0.backgroundColor = .clear + } + + private lazy var monthHeaderStackView = UIStackView().with { + $0.translatesAutoresizingMaskIntoConstraints = false + $0.distribution = .fill + $0.spacing = VDSLayout.space1X + $0.axis = .horizontal + $0.backgroundColor = .clear + } + + internal var containerView = View().with { + $0.clipsToBounds = true + $0.backgroundColor = .clear + } + internal var monthView = View() + internal var daysView = View() + internal let daysOfWeek = Date.capitalizedFirstLettersOfWeekdays + + private lazy var daysCollectionView: UICollectionView = { + let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout()) + collectionView.isScrollEnabled = false + collectionView.translatesAutoresizingMaskIntoConstraints = false + collectionView.delegate = self + collectionView.dataSource = self + collectionView.showsHorizontalScrollIndicator = false + collectionView.showsVerticalScrollIndicator = false + collectionView.backgroundColor = .clear + collectionView.register(collectionViewCell.self, forCellWithReuseIdentifier: collectionViewCell.identifier) + return collectionView + }() + + //-------------------------------------------------- + // MARK: - Lifecycle + //-------------------------------------------------- + open override func initialSetup() { + super.initialSetup() + } + + open override func setup() { + super.setup() + isAccessibilityElement = false + + // stackView + addSubview(containerView) + containerView + .pinTop() + .pinBottom() + .pinLeadingGreaterThanOrEqualTo() + .pinTrailingLessThanOrEqualTo() + .height(containerSize.height) + .width(containerSize.width) + containerView.centerXAnchor.constraint(equalTo: centerXAnchor).activate() + + // stackview + containerView.addSubview(stackView) + stackView + .pinTop(VDSLayout.space4X) + .pinLeading() + .pinTrailing() + .pinBottom(VDSLayout.space1X) + + // month label view, previous and next buttons + stackView.addArrangedSubview(monthView) + monthView.backgroundColor = .orange + monthView.heightAnchor.constraint(equalToConstant: 40).isActive = true + + // days Collection View + stackView.addArrangedSubview(daysCollectionView) + daysCollectionView.widthAnchor.constraint(equalTo: widthAnchor).activate() + daysCollectionView.heightAnchor.constraint(equalTo: monthView.heightAnchor).activate() + print("daysOfWeek: \(daysOfWeek)") + } + + open override func updateView() { + super.updateView() + daysCollectionView.reloadData() + } + + override open func layoutSubviews() { + super.layoutSubviews() + } + + open override func reset() { + super.reset() + } +} + +extension CalendarHeaderView: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { + + public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return daysOfWeek.count + } + + public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: collectionViewCell.identifier, for: indexPath) as? collectionViewCell else { return UICollectionViewCell() } + cell.updateTitle(text: daysOfWeek[indexPath.row], surface: surface) + return cell + } + + public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + return CGSize(width: 40, height: 40) + } + + public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat { + return VDSLayout.space1X + } + + public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat { + return VDSLayout.space1X + } +} + +private class collectionViewCell: UICollectionViewCell { + + static let identifier: String = String(describing: collectionViewCell.self) + + private let textColorConfiguration = SurfaceColorConfiguration(VDSColor.elementsSecondaryOnlight, VDSColor.elementsSecondaryOndark) + + private var title = Label().with { + $0.translatesAutoresizingMaskIntoConstraints = false + $0.textAlignment = .center + $0.numberOfLines = 1 + $0.textStyle = .bodySmall + $0.backgroundColor = .clear + $0.isAccessibilityElement = false + } + + //-------------------------------------------------- + // MARK: - Initializers + //-------------------------------------------------- + override init(frame: CGRect) { + super.init(frame: frame) + setupCell() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + setupCell() + } + + func setupCell() { + addSubview(title) + title.pinToSuperView() + } + + func updateTitle(text: String, surface: Surface) { + title.surface = surface + title.text = text + title.textColor = textColorConfiguration.getColor(surface) + title.backgroundColor = .clear + } +} diff --git a/VDS/Components/Calendar/CalendarReusableView.swift b/VDS/Components/Calendar/CalendarReusableView.swift index 96995006..b348cc30 100644 --- a/VDS/Components/Calendar/CalendarReusableView.swift +++ b/VDS/Components/Calendar/CalendarReusableView.swift @@ -14,6 +14,8 @@ class CalendarHeaderReusableView: UICollectionReusableView { ///Identifier for the Calendar Header Reusable View static let identifier: String = String(describing: CalendarHeaderReusableView.self) + private lazy var headerView = CalendarHeaderView() + override init(frame: CGRect) { super.init(frame: frame) } @@ -23,7 +25,7 @@ class CalendarHeaderReusableView: UICollectionReusableView { } func configure(with color: Bool) { - // Make a view and make in generic and dynamic + addSubview(headerView) } override func layoutSubviews() { diff --git a/VDS/Components/Calendar/Date+Extension.swift b/VDS/Components/Calendar/Date+Extension.swift new file mode 100644 index 00000000..60036ee2 --- /dev/null +++ b/VDS/Components/Calendar/Date+Extension.swift @@ -0,0 +1,20 @@ +// +// Date+Extension.swift +// VDS +// +// Created by Kanamarlapudi, Vasavi on 26/04/24. +// + +import Foundation + +extension Date { + static var capitalizedFirstLettersOfWeekdays: [String] { + let calendar = Calendar.current + let weekdays = calendar.shortWeekdaySymbols + + return weekdays.map { weekday in + guard let firstLetter = weekday.first else { return "" } + return String(firstLetter).capitalized + } + } +} From 36238890c79cf227ebfebb3d473a2f40614616bf Mon Sep 17 00:00:00 2001 From: vasavk Date: Tue, 30 Apr 2024 19:39:50 +0530 Subject: [PATCH 08/46] Digital ACT-191 ONEAPP-7016 story: added change log --- VDS.xcodeproj/project.pbxproj | 4 + VDS/Components/Calendar/CalendarChangeLog.txt | 84 +++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 VDS/Components/Calendar/CalendarChangeLog.txt diff --git a/VDS.xcodeproj/project.pbxproj b/VDS.xcodeproj/project.pbxproj index 33835770..e75dc4c1 100644 --- a/VDS.xcodeproj/project.pbxproj +++ b/VDS.xcodeproj/project.pbxproj @@ -27,6 +27,7 @@ 18FEA1B32BE0BC8700A56439 /* CalendarReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18FEA1B22BE0BC8700A56439 /* CalendarReusableView.swift */; }; 18FEA1B52BE0E63600A56439 /* Date+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18FEA1B42BE0E63600A56439 /* Date+Extension.swift */; }; 18FEA1B72BE0EBFE00A56439 /* CalendarHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18FEA1B62BE0EBFE00A56439 /* CalendarHeaderView.swift */; }; + 18FEA1B92BE1301700A56439 /* CalendarChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 18FEA1B82BE1301700A56439 /* CalendarChangeLog.txt */; }; 445BA07829C07B3D0036A7C5 /* Notification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 445BA07729C07B3D0036A7C5 /* Notification.swift */; }; 44604AD429CE186A00E62B51 /* NotificationButtonModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44604AD329CE186A00E62B51 /* NotificationButtonModel.swift */; }; 44604AD729CE196600E62B51 /* Line.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44604AD629CE196600E62B51 /* Line.swift */; }; @@ -223,6 +224,7 @@ 18FEA1B22BE0BC8700A56439 /* CalendarReusableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarReusableView.swift; sourceTree = ""; }; 18FEA1B42BE0E63600A56439 /* Date+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+Extension.swift"; sourceTree = ""; }; 18FEA1B62BE0EBFE00A56439 /* CalendarHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarHeaderView.swift; sourceTree = ""; }; + 18FEA1B82BE1301700A56439 /* CalendarChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = CalendarChangeLog.txt; sourceTree = ""; }; 445BA07729C07B3D0036A7C5 /* Notification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notification.swift; sourceTree = ""; }; 44604AD329CE186A00E62B51 /* NotificationButtonModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationButtonModel.swift; sourceTree = ""; }; 44604AD629CE196600E62B51 /* Line.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Line.swift; sourceTree = ""; }; @@ -439,6 +441,7 @@ 18FEA1B22BE0BC8700A56439 /* CalendarReusableView.swift */, 18FEA1B42BE0E63600A56439 /* Date+Extension.swift */, 18FEA1B62BE0EBFE00A56439 /* CalendarHeaderView.swift */, + 18FEA1B82BE1301700A56439 /* CalendarChangeLog.txt */, ); path = Calendar; sourceTree = ""; @@ -1094,6 +1097,7 @@ EAA5EEB928ECD24B003B3210 /* Icons.xcassets in Resources */, EAEEECA92B1F969700531FC2 /* TooltipChangeLog.txt in Resources */, 186D13CF2BBC36EF00986B53 /* DropdownSelectChangeLog.txt in Resources */, + 18FEA1B92BE1301700A56439 /* CalendarChangeLog.txt in Resources */, EAEEEC9C2B1F8F0700531FC2 /* TextLinkCaretChangeLog.txt in Resources */, EAA5EEE428F5B855003B3210 /* VerizonNHGDS-Light.otf in Resources */, 71B5FCBB2B95A0CA00269BCC /* PaginationChangeLog.txt in Resources */, diff --git a/VDS/Components/Calendar/CalendarChangeLog.txt b/VDS/Components/Calendar/CalendarChangeLog.txt new file mode 100644 index 00000000..ee6928a3 --- /dev/null +++ b/VDS/Components/Calendar/CalendarChangeLog.txt @@ -0,0 +1,84 @@ +MM/DD/YYYY +---------------- +- Initial Brand 3.0 handoff + +12/24/2021 +---------------- +- Replaced focusring colors (previously interactive/onlight/ondark) with accessibility/onlight/ondark colors +- Updated focus border name (previously interactive.focusring.onlight) with focusring.onlight/ondark +- Updated the SPECS with FormControl tokens and corner radius tokens + +02/24/2022 +---------------- +- Replaced Caret Left and Right icons with bold assets. + +02/28/2022 +---------------- +- Removed dev note from Hover state and added a dev note to Active state. + +03/11/2022 +---------------- +- Update Hover and Active states triggers for carets. Icon swap and color change for mouse-only, and color change for touch. + +05/25/2022 +---------------- +- Added date indicator feature (including legend), today’s date indicator, and size/spacing adjustments to support this. + +07/20/2022 +---------------- +- Added configuration page. +- To configuration added transparent background and border suppression. + +08/10/2022 +---------------- +- Updated inverted and default to light and dark surface. Also, updated dark to selected. + +08/16/2022 +---------------- +- Updated default date background to be transparent. Updated border configuration prop name to hideContainerBorder. +- Moved Width from Configurations to Layout and Spacing, and Calendar Indicator specs under Elements. + +11/30/2022 +---------------- +- Added "(web only)" to any instance of "keyboard focus" + +12/13/2022 +---------------- +- Replaced form border and focus border pixel values, styles & spacing with tokens. + +01/09/2023 +---------------- +- Updated Specs to use new SPEC Templates and SPEC DOC Components. + +01/10/2023 +---------------- +- Updated Anatomy item #8 to “Current Date” to match design doc (originally “Today’s Date) + +04/12/2023 +---------------- +- Updated palette colors for Current date. + +05/01/2023 +---------------- +- Updated Day Header Text Style from Bold to Regular +- Updated Date Disabled Text Style from Bold to Regular +- Updated Date Text Style from Bold to Regular +- Updated Current Date font color from blue to black on light and white on dark. +- Updated all frames to reflect new designs. + +05/09/2023 +---------------- +- Replaced Previous and Next carets with Button Icon in: +Anatomy +Configurations +States + +11/09/2023 +---------------- +- Added component tokens +- Applied component tokens to selected states on light and dark surfaces +- Removed redundant color specifications from other sections + +11/30/2023 +---------------- +- Revised selected container background inverse tokens from onlight/ondark to light/dark From 0d9c409736c357c69fb0981b029e3587ca4cba6b Mon Sep 17 00:00:00 2001 From: vasavk Date: Thu, 2 May 2024 07:53:42 +0530 Subject: [PATCH 09/46] Digital ACT-191 ONEAPP-7016 story: updated constraints and header month view --- VDS/Components/Calendar/Calendar.swift | 18 +++++-- .../Calendar/CalendarHeaderView.swift | 54 +++++++++++++++++-- .../Calendar/CalendarLegendView.swift | 8 +-- 3 files changed, 69 insertions(+), 11 deletions(-) diff --git a/VDS/Components/Calendar/Calendar.swift b/VDS/Components/Calendar/Calendar.swift index 0700bb55..3fa9de4a 100644 --- a/VDS/Components/Calendar/Calendar.swift +++ b/VDS/Components/Calendar/Calendar.swift @@ -71,10 +71,10 @@ open class CalendarBase: View { //-------------------------------------------------- // MARK: - Private Properties //-------------------------------------------------- - internal var containerSize: CGSize { CGSize(width: 320, height: 376) } //width:320/328 + internal var containerSize: CGSize { CGSize(width: 328, height: 376) } //width:320/328 private let cellItemSize = CGSize(width: 40, height: 40) - private let headerHeight = 104.0 - private let footerHeight = 56.0 + private let headerHeight = 88.0 + private let footerHeight = 40.0 private let items = 35 internal var containerView = View().with { @@ -135,7 +135,17 @@ open class CalendarBase: View { // Calendar View containerView.addSubview(collectionView) - collectionView.pinToSuperView() + collectionView + .pinTop(VDSLayout.space4X) + .pinBottom(VDSLayout.space4X) + .pinLeading(VDSFormControls.spaceInset) + .pinTrailing(VDSFormControls.spaceInset) + let width = containerSize.width - (2 * VDSFormControls.spaceInset) + collectionView.widthAnchor.constraint(equalToConstant: width).activate() + let height = containerSize.height - (2 * VDSLayout.space4X) + collectionView.heightAnchor.constraint(equalToConstant: height).activate() + collectionView.centerXAnchor.constraint(equalTo: containerView.centerXAnchor).activate() + collectionView.centerXAnchor.constraint(equalTo: containerView.centerXAnchor).activate() } open override func updateView() { diff --git a/VDS/Components/Calendar/CalendarHeaderView.swift b/VDS/Components/Calendar/CalendarHeaderView.swift index 2e591b94..bba9805c 100644 --- a/VDS/Components/Calendar/CalendarHeaderView.swift +++ b/VDS/Components/Calendar/CalendarHeaderView.swift @@ -33,7 +33,7 @@ open class CalendarHeaderView: View { //-------------------------------------------------- // MARK: - Private Properties //-------------------------------------------------- - internal var containerSize: CGSize { CGSize(width: 320, height: 104) } //width:320/328 + internal var containerSize: CGSize { CGSize(width: 304, height: 88) } //width:320/328 private lazy var stackView = UIStackView().with { $0.translatesAutoresizingMaskIntoConstraints = false @@ -56,9 +56,36 @@ open class CalendarHeaderView: View { $0.backgroundColor = .clear } internal var monthView = View() + internal var previousMonthView = View() + internal var nextMonthView = View() internal var daysView = View() internal let daysOfWeek = Date.capitalizedFirstLettersOfWeekdays + internal var previousButton = ButtonIcon().with { + $0.kind = .ghost + $0.iconName = .leftCaret + $0.iconOffset = .init(x: -2, y: 0) + $0.icon.size = .small + $0.size = .small + } + + internal var nextButton = ButtonIcon().with { + $0.kind = .ghost + $0.iconName = .rightCaret + $0.iconOffset = .init(x: 2, y: 0) + $0.icon.size = .small + $0.size = .small + } + + internal var monthYearLabel = Label().with { + $0.translatesAutoresizingMaskIntoConstraints = false + $0.textAlignment = .center + $0.numberOfLines = 1 + $0.textStyle = .boldBodySmall + $0.backgroundColor = .clear + $0.isAccessibilityElement = false + } + private lazy var daysCollectionView: UICollectionView = { let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout()) collectionView.isScrollEnabled = false @@ -72,6 +99,8 @@ open class CalendarHeaderView: View { return collectionView }() + internal var monthYearLabelTextColorConfiguration = SurfaceColorConfiguration(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark) + //-------------------------------------------------- // MARK: - Lifecycle //-------------------------------------------------- @@ -97,16 +126,33 @@ open class CalendarHeaderView: View { // stackview containerView.addSubview(stackView) stackView - .pinTop(VDSLayout.space4X) + .pinTop() .pinLeading() .pinTrailing() .pinBottom(VDSLayout.space1X) // month label view, previous and next buttons stackView.addArrangedSubview(monthView) - monthView.backgroundColor = .orange + monthView.backgroundColor = .clear monthView.heightAnchor.constraint(equalToConstant: 40).isActive = true + // month Header stack view + monthView.addSubview(monthHeaderStackView) + monthHeaderStackView.pinToSuperView() + + // previous button + monthHeaderStackView.addArrangedSubview(previousMonthView) + previousMonthView.widthAnchor.constraint(equalToConstant: 40).activate() + previousMonthView.addSubview(previousButton) + + // month year label + monthHeaderStackView.addArrangedSubview(monthYearLabel) + + // next button + monthHeaderStackView.addArrangedSubview(nextMonthView) + nextMonthView.widthAnchor.constraint(equalToConstant: 40).activate() + nextMonthView.addSubview(nextButton) + // days Collection View stackView.addArrangedSubview(daysCollectionView) daysCollectionView.widthAnchor.constraint(equalTo: widthAnchor).activate() @@ -117,6 +163,8 @@ open class CalendarHeaderView: View { open override func updateView() { super.updateView() daysCollectionView.reloadData() + monthYearLabel.text = "May 2024" + monthYearLabel.textColor = monthYearLabelTextColorConfiguration.getColor(surface) } override open func layoutSubviews() { diff --git a/VDS/Components/Calendar/CalendarLegendView.swift b/VDS/Components/Calendar/CalendarLegendView.swift index 78826ffd..9345483d 100644 --- a/VDS/Components/Calendar/CalendarLegendView.swift +++ b/VDS/Components/Calendar/CalendarLegendView.swift @@ -35,7 +35,7 @@ open class CalendarLegendView: View { //-------------------------------------------------- // MARK: - Private Properties //-------------------------------------------------- - internal var containerSize: CGSize { CGSize(width: 320, height: 56) } //width:320/328 + internal var containerSize: CGSize { CGSize(width: 304, height: 40) } //width:320/328 internal var containerView = View().with { $0.clipsToBounds = true @@ -73,9 +73,9 @@ open class CalendarLegendView: View { addSubview(containerView) containerView .pinTop(VDSLayout.space6X) - .pinBottom(VDSLayout.space4X) - .pinLeadingGreaterThanOrEqualTo(leadingAnchor, VDSLayout.space5X, .defaultLow) - .pinTrailingLessThanOrEqualTo(trailingAnchor, VDSLayout.space5X, .defaultHigh) + .pinBottom() + .pinLeadingGreaterThanOrEqualTo(leadingAnchor, VDSLayout.space3X, .defaultLow) + .pinTrailingLessThanOrEqualTo(trailingAnchor, VDSLayout.space3X, .defaultHigh) .height(containerSize.height) .width(containerSize.width) From 408effa9f0c9ea7fd8e9c27e035f3be32e50246d Mon Sep 17 00:00:00 2001 From: vasavk Date: Thu, 2 May 2024 17:51:56 +0530 Subject: [PATCH 10/46] Digital ACT-191 ONEAPP-7016 story: displaying days for calendar month view --- VDS.xcodeproj/project.pbxproj | 10 +- VDS/Components/Calendar/Calendar.swift | 49 ++++- .../CalendarDateCollectionViewCell.swift | 193 +++++++++++++++--- ...endView.swift => CalendarFooterView.swift} | 8 +- .../Calendar/CalendarHeaderView.swift | 12 +- .../Calendar/CalendarReusableView.swift | 2 +- VDS/Components/Calendar/Date+Extension.swift | 45 ++++ 7 files changed, 271 insertions(+), 48 deletions(-) rename VDS/Components/Calendar/{CalendarLegendView.swift => CalendarFooterView.swift} (97%) diff --git a/VDS.xcodeproj/project.pbxproj b/VDS.xcodeproj/project.pbxproj index e75dc4c1..e34e7772 100644 --- a/VDS.xcodeproj/project.pbxproj +++ b/VDS.xcodeproj/project.pbxproj @@ -10,6 +10,7 @@ 1808BEBC2BA41C3200129230 /* CarouselScrollbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1808BEBB2BA41C3200129230 /* CarouselScrollbar.swift */; }; 1808BEC02BA456B700129230 /* CarouselScrollbarChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 1808BEBF2BA456B700129230 /* CarouselScrollbarChangeLog.txt */; }; 1832AC572BA0791D008AE476 /* BreadcrumbCellItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1832AC562BA0791D008AE476 /* BreadcrumbCellItem.swift */; }; + 18408EDE2BE32C9900E8646B /* CalendarFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18408EDD2BE32C9900E8646B /* CalendarFooterView.swift */; }; 18450CF12BA1B19C009FDF2A /* BreadcrumbsChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 18450CF02BA1B19C009FDF2A /* BreadcrumbsChangeLog.txt */; }; 1855EC662BAABF2A002ACAC2 /* BreadcrumbItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1855EC652BAABF2A002ACAC2 /* BreadcrumbItemModel.swift */; }; 186B2A8A2B88DA7F001AB71F /* TextAreaChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 186B2A892B88DA7F001AB71F /* TextAreaChangeLog.txt */; }; @@ -23,7 +24,6 @@ 18B463A42BBD3C46005C4528 /* DropdownOptionModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18B463A32BBD3C46005C4528 /* DropdownOptionModel.swift */; }; 18BDEE822B75316E00452358 /* ButtonIconChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 18BDEE812B75316E00452358 /* ButtonIconChangeLog.txt */; }; 18FEA1AD2BDD137500A56439 /* CalendarIndicatorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18FEA1AC2BDD137500A56439 /* CalendarIndicatorModel.swift */; }; - 18FEA1B12BE0B69300A56439 /* CalendarLegendView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18FEA1B02BE0B69300A56439 /* CalendarLegendView.swift */; }; 18FEA1B32BE0BC8700A56439 /* CalendarReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18FEA1B22BE0BC8700A56439 /* CalendarReusableView.swift */; }; 18FEA1B52BE0E63600A56439 /* Date+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18FEA1B42BE0E63600A56439 /* Date+Extension.swift */; }; 18FEA1B72BE0EBFE00A56439 /* CalendarHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18FEA1B62BE0EBFE00A56439 /* CalendarHeaderView.swift */; }; @@ -207,6 +207,7 @@ 1808BEBB2BA41C3200129230 /* CarouselScrollbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarouselScrollbar.swift; sourceTree = ""; }; 1808BEBF2BA456B700129230 /* CarouselScrollbarChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = CarouselScrollbarChangeLog.txt; sourceTree = ""; }; 1832AC562BA0791D008AE476 /* BreadcrumbCellItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BreadcrumbCellItem.swift; sourceTree = ""; }; + 18408EDD2BE32C9900E8646B /* CalendarFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarFooterView.swift; sourceTree = ""; }; 18450CF02BA1B19C009FDF2A /* BreadcrumbsChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = BreadcrumbsChangeLog.txt; sourceTree = ""; }; 1855EC652BAABF2A002ACAC2 /* BreadcrumbItemModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BreadcrumbItemModel.swift; sourceTree = ""; }; 186B2A892B88DA7F001AB71F /* TextAreaChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = TextAreaChangeLog.txt; sourceTree = ""; }; @@ -220,7 +221,6 @@ 18B463A32BBD3C46005C4528 /* DropdownOptionModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DropdownOptionModel.swift; sourceTree = ""; }; 18BDEE812B75316E00452358 /* ButtonIconChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = ButtonIconChangeLog.txt; sourceTree = ""; }; 18FEA1AC2BDD137500A56439 /* CalendarIndicatorModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarIndicatorModel.swift; sourceTree = ""; }; - 18FEA1B02BE0B69300A56439 /* CalendarLegendView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarLegendView.swift; sourceTree = ""; }; 18FEA1B22BE0BC8700A56439 /* CalendarReusableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarReusableView.swift; sourceTree = ""; }; 18FEA1B42BE0E63600A56439 /* Date+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+Extension.swift"; sourceTree = ""; }; 18FEA1B62BE0EBFE00A56439 /* CalendarHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarHeaderView.swift; sourceTree = ""; }; @@ -436,11 +436,11 @@ children = ( 18A3F1292BD9298900498E4A /* Calendar.swift */, 18A3F1312BD944E800498E4A /* CalendarDateCollectionViewCell.swift */, + 18408EDD2BE32C9900E8646B /* CalendarFooterView.swift */, + 18FEA1B62BE0EBFE00A56439 /* CalendarHeaderView.swift */, 18FEA1AC2BDD137500A56439 /* CalendarIndicatorModel.swift */, - 18FEA1B02BE0B69300A56439 /* CalendarLegendView.swift */, 18FEA1B22BE0BC8700A56439 /* CalendarReusableView.swift */, 18FEA1B42BE0E63600A56439 /* Date+Extension.swift */, - 18FEA1B62BE0EBFE00A56439 /* CalendarHeaderView.swift */, 18FEA1B82BE1301700A56439 /* CalendarChangeLog.txt */, ); path = Calendar; @@ -1216,6 +1216,7 @@ EA985C7D297DAED300F2FF2E /* Primitive.swift in Sources */, EAF1FE9929D4850E00101452 /* Clickable.swift in Sources */, EAD0688E2A55F819002E3A2D /* Loader.swift in Sources */, + 18408EDE2BE32C9900E8646B /* CalendarFooterView.swift in Sources */, EAB5FEF829393A7200998C17 /* ButtonGroupConstants.swift in Sources */, EAA7456C2AB23E2000C1841F /* TooltipModel.swift in Sources */, EA3361AF288B26310071C351 /* FormFieldable.swift in Sources */, @@ -1229,7 +1230,6 @@ EA0B180A2AA78F9000F2D0CD /* UIEdgeInsets.swift in Sources */, EA985C1D296CD13600F2FF2E /* BundleManager.swift in Sources */, EA0B18052A9E2D2D00F2D0CD /* SelectorBase.swift in Sources */, - 18FEA1B12BE0B69300A56439 /* CalendarLegendView.swift in Sources */, EAC71A1D2A2E155A00E47A9F /* Checkbox.swift in Sources */, 18FEA1B32BE0BC8700A56439 /* CalendarReusableView.swift in Sources */, EAF7F0AB289B13FD00B287F5 /* TextStyleLabelAttribute.swift in Sources */, diff --git a/VDS/Components/Calendar/Calendar.swift b/VDS/Components/Calendar/Calendar.swift index 3fa9de4a..faa1bed4 100644 --- a/VDS/Components/Calendar/Calendar.swift +++ b/VDS/Components/Calendar/Calendar.swift @@ -54,7 +54,18 @@ open class CalendarBase: View { /// If provided, this is the date that will show as selected by the Calendar. /// If no value is provided, the current date will be used. If null is provided, no date will be selected. - open var selectedDate: Date? + open var selectedDate: Date? { + get { return _selectedDate } + set { + if let newValue { + _selectedDate = newValue + } else { + _selectedDate = Date() + } + //update views what needed + setNeedsUpdate() + } + } /// If provided, the calendar will be rendered with transparent background. open var transparentBackground: Bool = false @@ -71,6 +82,9 @@ open class CalendarBase: View { //-------------------------------------------------- // MARK: - Private Properties //-------------------------------------------------- + internal var _selectedDate: Date = Date() + private var dates: [Date] = [] + private var days: [String] = [] internal var containerSize: CGSize { CGSize(width: 328, height: 376) } //width:320/328 private let cellItemSize = CGSize(width: 40, height: 40) private let headerHeight = 88.0 @@ -150,6 +164,7 @@ open class CalendarBase: View { open override func updateView() { super.updateView() + self.fetchDates(with: selectedDate ?? Date()) collectionView.reloadData() containerView.layer.borderColor = containerBorderColorConfiguration.getColor(self).cgColor } @@ -161,6 +176,30 @@ open class CalendarBase: View { open override func reset() { super.reset() } + + func fetchDates(with date:Date) { + days.removeAll() + if let dates = selectedDate?.calendarDisplayDays { + self.dates = dates + for day in dates { + // code to be executed + if day.monthInt != selectedDate?.monthInt { + days.append("") + } else { + if #available(iOS 15.0, *) { + days.append(day.formatted(.dateTime.day())) + } else { + // Fallback on earlier versions + let dateFormatter: DateFormatter = DateFormatter() + dateFormatter.dateFormat = "d" + let dayStr: String = dateFormatter.string(from: day) + days.append(dayStr) + } + } + } +// print("days: \(days)") + } + } } extension CalendarBase: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { @@ -168,11 +207,12 @@ extension CalendarBase: UICollectionViewDelegate, UICollectionViewDataSource, UI // MARK: - UICollectionView Delegate & Datasource //-------------------------------------------------- public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { - items + days.count } public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CalendarDateCollectionViewCell.identifier, for: indexPath) as? CalendarDateCollectionViewCell else { return UICollectionViewCell() } + cell.configure(with: indicators, text: days[indexPath.row]) return cell } @@ -197,6 +237,11 @@ extension CalendarBase: UICollectionViewDelegate, UICollectionViewDataSource, UI return UICollectionReusableView() } + public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + let date = Calendar.current.date(byAdding: .day, value: 1, to: self.dates[indexPath.row])! + print("selected day: \(days[indexPath.row]), date: \(date)") + } + public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize { return CGSize(width: collectionView.frame.size.width, height: headerHeight) } diff --git a/VDS/Components/Calendar/CalendarDateCollectionViewCell.swift b/VDS/Components/Calendar/CalendarDateCollectionViewCell.swift index 156cce04..f1c50f15 100644 --- a/VDS/Components/Calendar/CalendarDateCollectionViewCell.swift +++ b/VDS/Components/Calendar/CalendarDateCollectionViewCell.swift @@ -9,46 +9,179 @@ import Foundation import UIKit import VDSTokens -///This is customised view for Calendar cell item +/// Calendar collection view cell final class CalendarDateCollectionViewCell: UICollectionViewCell { ///Identifier for the Calendar Date Cell static let identifier: String = String(describing: CalendarDateCollectionViewCell.self) - //-------------------------------------------------- - // MARK: - Private Properties - //-------------------------------------------------- -// internal var stackView: UIStackView = { -// return UIStackView().with { -// $0.translatesAutoresizingMaskIntoConstraints = false -// $0.axis = .horizontal -// $0.distribution = .fill -// $0.alignment = .center -// $0.spacing = VDSLayout.space2X -// $0.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal) -// $0.setContentHuggingPriority(.defaultHigh, for: .horizontal) -// } -// }() -// -// private lazy var selectionBackgroundView = View().with { -// $0.translatesAutoresizingMaskIntoConstraints = false -// $0.clipsToBounds = true -// $0.backgroundColor = .systemRed -// } -// -// private lazy var numberLabel = Label().with { -// $0.translatesAutoresizingMaskIntoConstraints = false -// $0.textAlignment = .center -// // $0.font -// // $0.textColor -// } - override init(frame:CGRect) { + private lazy var dateView = DateView() + + override init(frame: CGRect) { super.init(frame: frame) - contentView.backgroundColor = .link } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + func configure(with indicators: [CalendarBase.CalendarIndicatorModel], text: String) { + addSubview(dateView) + dateView.dateIndicators = indicators + dateView.numberLabel.text = text + } + + override func layoutSubviews() { + super.layoutSubviews() + } +} + +/// Date view to show Date number and indicator if applies +private class DateView : View { + + //-------------------------------------------------- + // 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) + } + + //-------------------------------------------------- + // MARK: - Public Properties + //-------------------------------------------------- + open var dateIndicators: [CalendarBase.CalendarIndicatorModel] = [] { didSet { setNeedsUpdate() } } + + open var numberLabel = Label().with { + $0.translatesAutoresizingMaskIntoConstraints = false + $0.textAlignment = .center + $0.textStyle = .bodySmall //isCurrentDate: .boldBodySmall + } + + //-------------------------------------------------- + // MARK: - Private Properties + //-------------------------------------------------- + internal var containerSize: CGSize { CGSize(width: 40, height: 40) } + + internal var containerView = View().with { + $0.clipsToBounds = true + } + + private lazy var stackView: UIStackView = { + return UIStackView().with { + $0.translatesAutoresizingMaskIntoConstraints = false + $0.axis = .horizontal + $0.distribution = .fill + $0.alignment = .center + $0.spacing = VDSLayout.space2X + $0.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal) + $0.setContentHuggingPriority(.defaultHigh, for: .horizontal) + } + }() + +// private lazy var indicatorsView = View().with { +// $0.translatesAutoresizingMaskIntoConstraints = false +// $0.clipsToBounds = true +// $0.backgroundColor = .systemRed +// } + + private var legendIndicator: View = View().with { + $0.translatesAutoresizingMaskIntoConstraints = false + $0.backgroundColor = .clear + $0.layer.borderWidth = 1.0 + } + + private lazy var shapeLayer = CAShapeLayer() + + private let indicatorColorConfiguration = SurfaceColorConfiguration(VDSColor.elementsSecondaryOnlight, VDSColor.elementsSecondaryOndark) + + + //-------------------------------------------------- + // MARK: - Lifecycle + //-------------------------------------------------- + open override func initialSetup() { + super.initialSetup() + } + + open override func setup() { + super.setup() + isAccessibilityElement = false + + addSubview(containerView) + containerView + .pinTop() + .pinBottom() + .pinLeadingGreaterThanOrEqualTo() + .pinTrailingLessThanOrEqualTo() + .height(containerSize.height) + .width(containerSize.width) + containerView.centerXAnchor.constraint(equalTo: centerXAnchor).activate() + + // Number label + containerView.addSubview(numberLabel) + numberLabel.pinToSuperView() + + // Indicators + if dateIndicators.count > 0 { + containerView.addSubview(stackView) + let topPos = containerSize.height * 0.6 + stackView.pinTop(topPos).pinBottom().pinLeading().pinTrailing().pinCenterY() + stackView.centerXAnchor.constraint(equalTo: centerXAnchor).activate() + let width = (dateIndicators.count * 8) + (8 * (dateIndicators.count - 1)) + stackView.widthAnchor.constraint(equalToConstant: CGFloat(width)).activate() + } + } + + open override func updateView() { + super.updateView() + } + + override open func layoutSubviews() { + super.layoutSubviews() + } + + open override func reset() { + super.reset() + } + + //-------------------------------------------------- + // MARK: - Private Methods + //-------------------------------------------------- + func configure(with indicators: [CalendarBase.CalendarIndicatorModel]) { + //TO DO: handling indicators - in progress + //show indicator + legendIndicator.pinLeading().pinTrailing().width(8).height(8).pinCenterY() + stackView.addArrangedSubview(legendIndicator) + updateIndicator(with: VDSColor.elementsSecondaryOnlight, surface: surface, clearFullCircle: false, drawSemiCircle: false) + } + + func updateIndicator(with color: UIColor, surface: Surface, clearFullCircle: Bool, drawSemiCircle: Bool){ + legendIndicator.backgroundColor = drawSemiCircle ? .clear : (clearFullCircle ? .clear : color) + legendIndicator.layer.borderColor = indicatorColorConfiguration.getColor(surface).cgColor + + self.layoutIfNeeded() + + legendIndicator.layer.cornerRadius = legendIndicator.frame.size.height / 2.0 + + guard drawSemiCircle else { return } + + let center = CGPoint(x: legendIndicator.frame.size.width/2, y: legendIndicator.frame.size.height/2) + let path = UIBezierPath() + path.move(to: center) + path.addArc(withCenter: center, radius: center.x, startAngle: 2 * .pi, endAngle: .pi, clockwise: true) + path.close() + shapeLayer.path = path.cgPath + shapeLayer.fillColor = color.cgColor + + guard legendIndicator.layer.sublayers?.contains(shapeLayer) ?? true else { return } + legendIndicator.layer.addSublayer(shapeLayer) + } } diff --git a/VDS/Components/Calendar/CalendarLegendView.swift b/VDS/Components/Calendar/CalendarFooterView.swift similarity index 97% rename from VDS/Components/Calendar/CalendarLegendView.swift rename to VDS/Components/Calendar/CalendarFooterView.swift index 9345483d..84d76cab 100644 --- a/VDS/Components/Calendar/CalendarLegendView.swift +++ b/VDS/Components/Calendar/CalendarFooterView.swift @@ -1,5 +1,5 @@ // -// CalendarLegendView.swift +// CalendarFooterView.swift // VDS // // Created by Kanamarlapudi, Vasavi on 29/04/24. @@ -10,8 +10,8 @@ import UIKit import VDSTokens import Combine -/// Legend view to show array of indicators as calendar footer view. -open class CalendarLegendView: View { +/// Footer view to show indicators data. +open class CalendarFooterView: View { //-------------------------------------------------- // MARK: - Initializers //-------------------------------------------------- @@ -103,7 +103,7 @@ open class CalendarLegendView: View { } } -extension CalendarLegendView: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { +extension CalendarFooterView: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return items.count diff --git a/VDS/Components/Calendar/CalendarHeaderView.swift b/VDS/Components/Calendar/CalendarHeaderView.swift index bba9805c..44f5462f 100644 --- a/VDS/Components/Calendar/CalendarHeaderView.swift +++ b/VDS/Components/Calendar/CalendarHeaderView.swift @@ -43,7 +43,7 @@ open class CalendarHeaderView: View { $0.backgroundColor = .clear } - private lazy var monthHeaderStackView = UIStackView().with { + private lazy var monthYearHeaderStackView = UIStackView().with { $0.translatesAutoresizingMaskIntoConstraints = false $0.distribution = .fill $0.spacing = VDSLayout.space1X @@ -137,19 +137,19 @@ open class CalendarHeaderView: View { monthView.heightAnchor.constraint(equalToConstant: 40).isActive = true // month Header stack view - monthView.addSubview(monthHeaderStackView) - monthHeaderStackView.pinToSuperView() + monthView.addSubview(monthYearHeaderStackView) + monthYearHeaderStackView.pinToSuperView() // previous button - monthHeaderStackView.addArrangedSubview(previousMonthView) + monthYearHeaderStackView.addArrangedSubview(previousMonthView) previousMonthView.widthAnchor.constraint(equalToConstant: 40).activate() previousMonthView.addSubview(previousButton) // month year label - monthHeaderStackView.addArrangedSubview(monthYearLabel) + monthYearHeaderStackView.addArrangedSubview(monthYearLabel) // next button - monthHeaderStackView.addArrangedSubview(nextMonthView) + monthYearHeaderStackView.addArrangedSubview(nextMonthView) nextMonthView.widthAnchor.constraint(equalToConstant: 40).activate() nextMonthView.addSubview(nextButton) diff --git a/VDS/Components/Calendar/CalendarReusableView.swift b/VDS/Components/Calendar/CalendarReusableView.swift index b348cc30..33bedfe2 100644 --- a/VDS/Components/Calendar/CalendarReusableView.swift +++ b/VDS/Components/Calendar/CalendarReusableView.swift @@ -39,7 +39,7 @@ class CalendarFooterReusableView: UICollectionReusableView { ///Identifier for the Calendar Footer Reusable View static let identifier: String = String(describing: CalendarFooterReusableView.self) - private lazy var footerView = CalendarLegendView() + private lazy var footerView = CalendarFooterView() override init(frame: CGRect) { super.init(frame: frame) diff --git a/VDS/Components/Calendar/Date+Extension.swift b/VDS/Components/Calendar/Date+Extension.swift index 60036ee2..07284507 100644 --- a/VDS/Components/Calendar/Date+Extension.swift +++ b/VDS/Components/Calendar/Date+Extension.swift @@ -8,6 +8,7 @@ import Foundation extension Date { + static var firstDayOfWeek = Calendar.current.firstWeekday static var capitalizedFirstLettersOfWeekdays: [String] { let calendar = Calendar.current let weekdays = calendar.shortWeekdaySymbols @@ -17,4 +18,48 @@ extension Date { return String(firstLetter).capitalized } } + + var startOfMonth: Date { + Calendar.current.dateInterval(of: .month, for: self)!.start + } + + var endOfMonth: Date { + let lastDay = Calendar.current.dateInterval(of: .month, for: self)!.end + return Calendar.current.date(byAdding: .day, value: -1, to: lastDay)! + } + + var numberOfDaysInMonth: Int { + Calendar.current.component(.day, from: endOfMonth) + } + + var firstWeekDayBeforeStart: Date { + let startOfMonthWeekday = Calendar.current.component(.weekday, from: startOfMonth) + var numberFromPreviousMonth = startOfMonthWeekday - Self.firstDayOfWeek + if numberFromPreviousMonth < 0 { + numberFromPreviousMonth += 7 // Adjust to a 0-6 range if negative + } + return Calendar.current.date(byAdding: .day, value: -numberFromPreviousMonth, to: startOfMonth)! + } + + var calendarDisplayDays: [Date] { + var days: [Date] = [] + // Start with days from the previous month to fill the grid + let firstDisplayDay = firstWeekDayBeforeStart + var day = firstDisplayDay + while day < startOfMonth { + days.append(day) + day = Calendar.current.date(byAdding: .day, value: 1, to: day)! + } + // Add days of the current month + for dayOffset in 0.. Date: Fri, 3 May 2024 18:29:32 +0530 Subject: [PATCH 11/46] Digital ACT-191 ONEAPP-7016 story: Displays indicator for day based on the indicators data --- VDS/Components/Calendar/Calendar.swift | 51 +++++--- .../CalendarDateCollectionViewCell.swift | 112 ++++++++++-------- 2 files changed, 95 insertions(+), 68 deletions(-) diff --git a/VDS/Components/Calendar/Calendar.swift b/VDS/Components/Calendar/Calendar.swift index faa1bed4..f9cb16cf 100644 --- a/VDS/Components/Calendar/Calendar.swift +++ b/VDS/Components/Calendar/Calendar.swift @@ -134,7 +134,6 @@ open class CalendarBase: View { open override func setup() { super.setup() isAccessibilityElement = false - addSubview(containerView) containerView .pinTop() @@ -147,19 +146,20 @@ open class CalendarBase: View { containerView.centerXAnchor.constraint(equalTo: centerXAnchor).activate() containerView.layer.borderWidth = VDSFormControls.borderWidth + let spacing = CGFloat(VDSLayout.space2X) //CGFloat(VDSFormControls.spaceInset) // + // Calendar View containerView.addSubview(collectionView) collectionView .pinTop(VDSLayout.space4X) .pinBottom(VDSLayout.space4X) - .pinLeading(VDSFormControls.spaceInset) - .pinTrailing(VDSFormControls.spaceInset) - let width = containerSize.width - (2 * VDSFormControls.spaceInset) + .pinLeading(spacing) + .pinTrailing(spacing) + let width = containerSize.width - (2 * spacing) collectionView.widthAnchor.constraint(equalToConstant: width).activate() let height = containerSize.height - (2 * VDSLayout.space4X) collectionView.heightAnchor.constraint(equalToConstant: height).activate() collectionView.centerXAnchor.constraint(equalTo: containerView.centerXAnchor).activate() - collectionView.centerXAnchor.constraint(equalTo: containerView.centerXAnchor).activate() } open override func updateView() { @@ -177,29 +177,36 @@ open class CalendarBase: View { super.reset() } + //-------------------------------------------------- + // MARK: - Private Methods + //-------------------------------------------------- func fetchDates(with date:Date) { days.removeAll() if let dates = selectedDate?.calendarDisplayDays { self.dates = dates - for day in dates { + for date in dates { // code to be executed - if day.monthInt != selectedDate?.monthInt { + if date.monthInt != selectedDate?.monthInt { days.append("") } else { - if #available(iOS 15.0, *) { - days.append(day.formatted(.dateTime.day())) - } else { - // Fallback on earlier versions - let dateFormatter: DateFormatter = DateFormatter() - dateFormatter.dateFormat = "d" - let dayStr: String = dateFormatter.string(from: day) - days.append(dayStr) - } + days.append(getDay(with: date)) } } -// print("days: \(days)") } } + + func getDay(with date:Date) -> String { + if #available(iOS 15.0, *) { + return date.formatted(.dateTime.day()) + } else { + // Fallback on earlier versions + let dateFormatter: DateFormatter = DateFormatter() + dateFormatter.dateFormat = "d" + let day: String = dateFormatter.string(from: date) + return day + } + } + } extension CalendarBase: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { @@ -212,7 +219,13 @@ extension CalendarBase: UICollectionViewDelegate, UICollectionViewDataSource, UI public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CalendarDateCollectionViewCell.identifier, for: indexPath) as? CalendarDateCollectionViewCell else { return UICollectionViewCell() } - cell.configure(with: indicators, text: days[indexPath.row]) + var indicatorCount = 0 + for x in (0...(self.indicators.count-1)) { + if (self.days[indexPath.row] == self.getDay(with: self.indicators[x].date)) { + indicatorCount += 1 + } + } + cell.configure(with: surface, indicators: indicators, text: days[indexPath.row], indicatorCount: indicatorCount) return cell } @@ -239,7 +252,7 @@ extension CalendarBase: UICollectionViewDelegate, UICollectionViewDataSource, UI public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { let date = Calendar.current.date(byAdding: .day, value: 1, to: self.dates[indexPath.row])! - print("selected day: \(days[indexPath.row]), date: \(date)") +// print("selected day: \(days[indexPath.row]), date: \(date)") } public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize { diff --git a/VDS/Components/Calendar/CalendarDateCollectionViewCell.swift b/VDS/Components/Calendar/CalendarDateCollectionViewCell.swift index f1c50f15..fd33611b 100644 --- a/VDS/Components/Calendar/CalendarDateCollectionViewCell.swift +++ b/VDS/Components/Calendar/CalendarDateCollectionViewCell.swift @@ -26,10 +26,11 @@ final class CalendarDateCollectionViewCell: UICollectionViewCell { fatalError("init(coder:) has not been implemented") } - func configure(with indicators: [CalendarBase.CalendarIndicatorModel], text: String) { + func configure(with surface: Surface, indicators: [CalendarBase.CalendarIndicatorModel], text: String, indicatorCount: Int) { addSubview(dateView) - dateView.dateIndicators = indicators dateView.numberLabel.text = text + dateView.indicatorCount = indicatorCount + dateView.dateIndicators = indicators } override func layoutSubviews() { @@ -58,8 +59,10 @@ private class DateView : View { //-------------------------------------------------- // MARK: - Public Properties //-------------------------------------------------- + open var indicatorCount: Int = 0 + open var dateIndicators: [CalendarBase.CalendarIndicatorModel] = [] { didSet { setNeedsUpdate() } } - + open var numberLabel = Label().with { $0.translatesAutoresizingMaskIntoConstraints = false $0.textAlignment = .center @@ -70,39 +73,26 @@ private class DateView : View { // MARK: - Private Properties //-------------------------------------------------- internal var containerSize: CGSize { CGSize(width: 40, height: 40) } - + internal var containerView = View().with { $0.clipsToBounds = true } - + private lazy var stackView: UIStackView = { return UIStackView().with { $0.translatesAutoresizingMaskIntoConstraints = false $0.axis = .horizontal $0.distribution = .fill $0.alignment = .center - $0.spacing = VDSLayout.space2X + $0.spacing = VDSLayout.space1X $0.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal) $0.setContentHuggingPriority(.defaultHigh, for: .horizontal) } }() -// private lazy var indicatorsView = View().with { -// $0.translatesAutoresizingMaskIntoConstraints = false -// $0.clipsToBounds = true -// $0.backgroundColor = .systemRed -// } - - private var legendIndicator: View = View().with { - $0.translatesAutoresizingMaskIntoConstraints = false - $0.backgroundColor = .clear - $0.layer.borderWidth = 1.0 - } - private lazy var shapeLayer = CAShapeLayer() - - private let indicatorColorConfiguration = SurfaceColorConfiguration(VDSColor.elementsSecondaryOnlight, VDSColor.elementsSecondaryOndark) + private let indicatorColorConfiguration = SurfaceColorConfiguration(VDSColor.elementsSecondaryOnlight, VDSColor.elementsSecondaryOndark) //-------------------------------------------------- // MARK: - Lifecycle @@ -114,7 +104,6 @@ private class DateView : View { open override func setup() { super.setup() isAccessibilityElement = false - addSubview(containerView) containerView .pinTop() @@ -124,24 +113,33 @@ private class DateView : View { .height(containerSize.height) .width(containerSize.width) containerView.centerXAnchor.constraint(equalTo: centerXAnchor).activate() - + // Number label containerView.addSubview(numberLabel) numberLabel.pinToSuperView() - + // Indicators - if dateIndicators.count > 0 { - containerView.addSubview(stackView) - let topPos = containerSize.height * 0.6 - stackView.pinTop(topPos).pinBottom().pinLeading().pinTrailing().pinCenterY() - stackView.centerXAnchor.constraint(equalTo: centerXAnchor).activate() - let width = (dateIndicators.count * 8) + (8 * (dateIndicators.count - 1)) - stackView.widthAnchor.constraint(equalToConstant: CGFloat(width)).activate() - } + containerView.addSubview(stackView) + let topPos = containerSize.height * 0.6 + stackView.pinTop(topPos).pinBottom().pinTopGreaterThanOrEqualTo().pinTrailingLessThanOrEqualTo().pinCenterY() + stackView.centerXAnchor.constraint(equalTo: centerXAnchor).activate() } open override func updateView() { super.updateView() + numberLabel.surface = surface + numberLabel.isEnabled = isEnabled + + stackView.arrangedSubviews.forEach { $0.removeFromSuperview() } + if indicatorCount > 0 { + let width = (indicatorCount * 8) + (Int(VDSLayout.space1X) * (indicatorCount - 1)) + stackView.widthAnchor.constraint(equalToConstant: CGFloat(width)).activate() + for x in (0...(dateIndicators.count-1)) { + if (self.numberLabel.text == self.getDay(with: dateIndicators[x].date)) { + addIndicator(with: VDSColor.elementsSecondaryOnlight, surface: surface, clearFullCircle: (x == 1), drawSemiCircle: (x == 2)) + } + } + } } override open func layoutSubviews() { @@ -150,30 +148,33 @@ private class DateView : View { open override func reset() { super.reset() + numberLabel.textStyle = .bodySmall } - //-------------------------------------------------- - // MARK: - Private Methods - //-------------------------------------------------- - func configure(with indicators: [CalendarBase.CalendarIndicatorModel]) { - //TO DO: handling indicators - in progress - //show indicator - legendIndicator.pinLeading().pinTrailing().width(8).height(8).pinCenterY() - stackView.addArrangedSubview(legendIndicator) - updateIndicator(with: VDSColor.elementsSecondaryOnlight, surface: surface, clearFullCircle: false, drawSemiCircle: false) - } - - func updateIndicator(with color: UIColor, surface: Surface, clearFullCircle: Bool, drawSemiCircle: Bool){ - legendIndicator.backgroundColor = drawSemiCircle ? .clear : (clearFullCircle ? .clear : color) - legendIndicator.layer.borderColor = indicatorColorConfiguration.getColor(surface).cgColor + //-------------------------------------------------- + // MARK: - Private Methods + //-------------------------------------------------- + func addIndicator(with color: UIColor, surface: Surface, clearFullCircle: Bool, drawSemiCircle: Bool) { + // add indicator + let indicatorView: View = View().with { + $0.translatesAutoresizingMaskIntoConstraints = false + $0.backgroundColor = .clear + $0.layer.borderWidth = 1.0 + } + indicatorView.pinLeading().pinTrailing().width(8).height(8).pinCenterY() + stackView.addArrangedSubview(indicatorView) + + // update indicator + indicatorView.backgroundColor = drawSemiCircle ? .clear : (clearFullCircle ? .clear : color) + indicatorView.layer.borderColor = indicatorColorConfiguration.getColor(surface).cgColor self.layoutIfNeeded() - legendIndicator.layer.cornerRadius = legendIndicator.frame.size.height / 2.0 + indicatorView.layer.cornerRadius = indicatorView.frame.size.height / 2.0 guard drawSemiCircle else { return } - let center = CGPoint(x: legendIndicator.frame.size.width/2, y: legendIndicator.frame.size.height/2) + let center = CGPoint(x: indicatorView.frame.size.width/2, y: indicatorView.frame.size.height/2) let path = UIBezierPath() path.move(to: center) path.addArc(withCenter: center, radius: center.x, startAngle: 2 * .pi, endAngle: .pi, clockwise: true) @@ -181,7 +182,20 @@ private class DateView : View { shapeLayer.path = path.cgPath shapeLayer.fillColor = color.cgColor - guard legendIndicator.layer.sublayers?.contains(shapeLayer) ?? true else { return } - legendIndicator.layer.addSublayer(shapeLayer) + guard indicatorView.layer.sublayers?.contains(shapeLayer) ?? true else { return } + indicatorView.layer.addSublayer(shapeLayer) } + + func getDay(with date:Date) -> String { + if #available(iOS 15.0, *) { + return date.formatted(.dateTime.day()) + } else { + // Fallback on earlier versions + let dateFormatter: DateFormatter = DateFormatter() + dateFormatter.dateFormat = "d" + let day: String = dateFormatter.string(from: date) + return day + } + } + } From f043ac74d38787e390148ab6a0ac1bc068f4117b Mon Sep 17 00:00:00 2001 From: vasavk Date: Fri, 3 May 2024 19:59:49 +0530 Subject: [PATCH 12/46] Digital ACT-191 ONEAPP-7016 story: updating surface for all childviews --- VDS/Components/Calendar/Calendar.swift | 8 ++++---- .../Calendar/CalendarDateCollectionViewCell.swift | 3 ++- VDS/Components/Calendar/CalendarFooterView.swift | 5 +++-- VDS/Components/Calendar/CalendarHeaderView.swift | 6 +++++- VDS/Components/Calendar/CalendarReusableView.swift | 6 ++++-- 5 files changed, 18 insertions(+), 10 deletions(-) diff --git a/VDS/Components/Calendar/Calendar.swift b/VDS/Components/Calendar/Calendar.swift index f9cb16cf..3e1442e1 100644 --- a/VDS/Components/Calendar/Calendar.swift +++ b/VDS/Components/Calendar/Calendar.swift @@ -146,7 +146,7 @@ open class CalendarBase: View { containerView.centerXAnchor.constraint(equalTo: centerXAnchor).activate() containerView.layer.borderWidth = VDSFormControls.borderWidth - let spacing = CGFloat(VDSLayout.space2X) //CGFloat(VDSFormControls.spaceInset) // + let spacing = CGFloat(VDSLayout.space2X) //CGFloat(VDSFormControls.spaceInset) // // Calendar View containerView.addSubview(collectionView) @@ -220,7 +220,7 @@ extension CalendarBase: UICollectionViewDelegate, UICollectionViewDataSource, UI public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CalendarDateCollectionViewCell.identifier, for: indexPath) as? CalendarDateCollectionViewCell else { return UICollectionViewCell() } var indicatorCount = 0 - for x in (0...(self.indicators.count-1)) { + for x in (0...(self.indicators.count-1)) { if (self.days[indexPath.row] == self.getDay(with: self.indicators[x].date)) { indicatorCount += 1 } @@ -235,7 +235,7 @@ extension CalendarBase: UICollectionViewDelegate, UICollectionViewDataSource, UI guard let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: CalendarHeaderReusableView.identifier, for: indexPath) as? CalendarHeaderReusableView else { return UICollectionReusableView() } - header.configure(with: true) + header.configure(with: surface) return header } else { // Footer @@ -243,7 +243,7 @@ extension CalendarBase: UICollectionViewDelegate, UICollectionViewDataSource, UI guard let footer = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: CalendarFooterReusableView.identifier, for: indexPath) as? CalendarFooterReusableView else { return UICollectionReusableView() } - footer.configure(with: indicators) + footer.configure(with: surface, indicators: indicators) return footer } } diff --git a/VDS/Components/Calendar/CalendarDateCollectionViewCell.swift b/VDS/Components/Calendar/CalendarDateCollectionViewCell.swift index fd33611b..68917069 100644 --- a/VDS/Components/Calendar/CalendarDateCollectionViewCell.swift +++ b/VDS/Components/Calendar/CalendarDateCollectionViewCell.swift @@ -28,6 +28,7 @@ final class CalendarDateCollectionViewCell: UICollectionViewCell { func configure(with surface: Surface, indicators: [CalendarBase.CalendarIndicatorModel], text: String, indicatorCount: Int) { addSubview(dateView) + dateView.surface = surface dateView.numberLabel.text = text dateView.indicatorCount = indicatorCount dateView.dateIndicators = indicators @@ -120,7 +121,7 @@ private class DateView : View { // Indicators containerView.addSubview(stackView) - let topPos = containerSize.height * 0.6 + let topPos = containerSize.height * 0.7 stackView.pinTop(topPos).pinBottom().pinTopGreaterThanOrEqualTo().pinTrailingLessThanOrEqualTo().pinCenterY() stackView.centerXAnchor.constraint(equalTo: centerXAnchor).activate() } diff --git a/VDS/Components/Calendar/CalendarFooterView.swift b/VDS/Components/Calendar/CalendarFooterView.swift index 84d76cab..6574dc84 100644 --- a/VDS/Components/Calendar/CalendarFooterView.swift +++ b/VDS/Components/Calendar/CalendarFooterView.swift @@ -127,14 +127,14 @@ private class LegendCollectionViewCell: UICollectionViewCell { private let indicatorColorConfiguration = SurfaceColorConfiguration(VDSColor.elementsSecondaryOnlight, VDSColor.elementsSecondaryOndark) - private var title = Label().with { + private var title: Label = Label().with { $0.translatesAutoresizingMaskIntoConstraints = false $0.textAlignment = .left $0.numberOfLines = 1 $0.textStyle = .bodySmall - $0.backgroundColor = .clear $0.isAccessibilityElement = false } + private var legendIndicatorWrapper: View = View().with { $0.translatesAutoresizingMaskIntoConstraints = false $0.backgroundColor = .clear @@ -180,6 +180,7 @@ private class LegendCollectionViewCell: UICollectionViewCell { } func updateTitle(text: String, color: UIColor, surface: Surface, clearFullcircle: Bool, drawSemiCircle: Bool) { + title.surface = surface title.text = text title.textColor = textColorConfiguration.getColor(surface) diff --git a/VDS/Components/Calendar/CalendarHeaderView.swift b/VDS/Components/Calendar/CalendarHeaderView.swift index 44f5462f..f217ae40 100644 --- a/VDS/Components/Calendar/CalendarHeaderView.swift +++ b/VDS/Components/Calendar/CalendarHeaderView.swift @@ -130,6 +130,7 @@ open class CalendarHeaderView: View { .pinLeading() .pinTrailing() .pinBottom(VDSLayout.space1X) + stackView.centerXAnchor.constraint(equalTo: containerView.centerXAnchor).activate() // month label view, previous and next buttons stackView.addArrangedSubview(monthView) @@ -157,11 +158,14 @@ open class CalendarHeaderView: View { stackView.addArrangedSubview(daysCollectionView) daysCollectionView.widthAnchor.constraint(equalTo: widthAnchor).activate() daysCollectionView.heightAnchor.constraint(equalTo: monthView.heightAnchor).activate() - print("daysOfWeek: \(daysOfWeek)") + daysCollectionView.centerXAnchor.constraint(equalTo: containerView.centerXAnchor).activate() } open override func updateView() { super.updateView() + monthYearLabel.surface = surface + previousButton.surface = surface + nextButton.surface = surface daysCollectionView.reloadData() monthYearLabel.text = "May 2024" monthYearLabel.textColor = monthYearLabelTextColorConfiguration.getColor(surface) diff --git a/VDS/Components/Calendar/CalendarReusableView.swift b/VDS/Components/Calendar/CalendarReusableView.swift index 33bedfe2..3a61e635 100644 --- a/VDS/Components/Calendar/CalendarReusableView.swift +++ b/VDS/Components/Calendar/CalendarReusableView.swift @@ -24,7 +24,8 @@ class CalendarHeaderReusableView: UICollectionReusableView { fatalError("init(coder:) has not been implemented") } - func configure(with color: Bool) { + func configure(with surface: Surface) { + headerView.surface = surface addSubview(headerView) } @@ -49,8 +50,9 @@ class CalendarFooterReusableView: UICollectionReusableView { fatalError("init(coder:) has not been implemented") } - func configure(with indicators: [CalendarBase.CalendarIndicatorModel]) { + func configure(with surface: Surface, indicators: [CalendarBase.CalendarIndicatorModel]) { footerView.items = indicators + footerView.surface = surface addSubview(footerView) } From 3eaf29f3f1ec7f0c3ef482b99f00c683a4bf6d55 Mon Sep 17 00:00:00 2001 From: vasavk Date: Mon, 6 May 2024 09:47:02 +0530 Subject: [PATCH 13/46] Digital ACT-191 ONEAPP-7016 story: hide/show container border --- VDS/Components/Calendar/Calendar.swift | 31 ++++++++++++------- .../Calendar/CalendarReusableView.swift | 8 ----- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/VDS/Components/Calendar/Calendar.swift b/VDS/Components/Calendar/Calendar.swift index 3e1442e1..41890756 100644 --- a/VDS/Components/Calendar/Calendar.swift +++ b/VDS/Components/Calendar/Calendar.swift @@ -33,7 +33,7 @@ open class CalendarBase: View { // MARK: - Public Properties //-------------------------------------------------- /// If set to true, the calendar will not have a border. - open var hideContainerBorder: Bool = false + open var hideContainerBorder: Bool = false { didSet { setNeedsUpdate() } } /// If set to true, the calendar will not have current date indication. open var hideCurrentDateIndicator: Bool = false @@ -123,7 +123,7 @@ open class CalendarBase: View { $0.setSurfaceColors(VDSFormControlsColor.backgroundOnlight, VDSFormControlsColor.backgroundOndark, forState: .normal) $0.setSurfaceColors(VDSFormControlsColor.backgroundOnlight, VDSFormControlsColor.backgroundOndark, forState: .disabled) } - + //-------------------------------------------------- // MARK: - Lifecycle //-------------------------------------------------- @@ -144,9 +144,8 @@ open class CalendarBase: View { .width(containerSize.width) containerView.centerXAnchor.constraint(equalTo: centerXAnchor).activate() - containerView.layer.borderWidth = VDSFormControls.borderWidth - - let spacing = CGFloat(VDSLayout.space2X) //CGFloat(VDSFormControls.spaceInset) // + + let spacing = CGFloat(VDSFormControls.spaceInset) //CGFloat(VDSLayout.space2X) // Calendar View containerView.addSubview(collectionView) @@ -166,7 +165,15 @@ open class CalendarBase: View { super.updateView() self.fetchDates(with: selectedDate ?? Date()) collectionView.reloadData() - containerView.layer.borderColor = containerBorderColorConfiguration.getColor(self).cgColor + if hideContainerBorder { + layer.borderColor = nil + layer.borderWidth = 0 + layer.cornerRadius = 0 + } else { + layer.borderColor = containerBorderColorConfiguration.getColor(self).cgColor + layer.borderWidth = VDSFormControls.borderWidth + layer.cornerRadius = VDSFormControls.borderRadius + } } override open func layoutSubviews() { @@ -220,9 +227,11 @@ extension CalendarBase: UICollectionViewDelegate, UICollectionViewDataSource, UI public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CalendarDateCollectionViewCell.identifier, for: indexPath) as? CalendarDateCollectionViewCell else { return UICollectionViewCell() } var indicatorCount = 0 - for x in (0...(self.indicators.count-1)) { - if (self.days[indexPath.row] == self.getDay(with: self.indicators[x].date)) { - indicatorCount += 1 + if self.indicators.count > 0 { + for x in (0...(self.indicators.count-1)) { + if (self.days[indexPath.row] == self.getDay(with: self.indicators[x].date)) { + indicatorCount += 1 + } } } cell.configure(with: surface, indicators: indicators, text: days[indexPath.row], indicatorCount: indicatorCount) @@ -251,8 +260,8 @@ extension CalendarBase: UICollectionViewDelegate, UICollectionViewDataSource, UI } public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { - let date = Calendar.current.date(byAdding: .day, value: 1, to: self.dates[indexPath.row])! -// print("selected day: \(days[indexPath.row]), date: \(date)") +// let selectedDate = Calendar.current.date(byAdding: .day, value: 1, to: self.dates[indexPath.row])! +// print("selected day: \(days[indexPath.row]), date: \(selectedDate)") } public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize { diff --git a/VDS/Components/Calendar/CalendarReusableView.swift b/VDS/Components/Calendar/CalendarReusableView.swift index 3a61e635..ebb2d269 100644 --- a/VDS/Components/Calendar/CalendarReusableView.swift +++ b/VDS/Components/Calendar/CalendarReusableView.swift @@ -28,10 +28,6 @@ class CalendarHeaderReusableView: UICollectionReusableView { headerView.surface = surface addSubview(headerView) } - - override func layoutSubviews() { - super.layoutSubviews() - } } /// Custom footer view @@ -55,8 +51,4 @@ class CalendarFooterReusableView: UICollectionReusableView { footerView.surface = surface addSubview(footerView) } - - override func layoutSubviews() { - super.layoutSubviews() - } } From e980d816b327feca616b095c89f5f65b9e38edfd Mon Sep 17 00:00:00 2001 From: vasavk Date: Mon, 6 May 2024 12:30:32 +0530 Subject: [PATCH 14/46] Digital ACT-191 ONEAPP-7016 story: applying selected/unselected properties and updating selected date on change --- VDS/Components/Calendar/Calendar.swift | 55 +++++++++-------- .../CalendarDateCollectionViewCell.swift | 59 +++++++++++++++---- 2 files changed, 78 insertions(+), 36 deletions(-) diff --git a/VDS/Components/Calendar/Calendar.swift b/VDS/Components/Calendar/Calendar.swift index 41890756..cfd2c11d 100644 --- a/VDS/Components/Calendar/Calendar.swift +++ b/VDS/Components/Calendar/Calendar.swift @@ -54,15 +54,10 @@ open class CalendarBase: View { /// If provided, this is the date that will show as selected by the Calendar. /// If no value is provided, the current date will be used. If null is provided, no date will be selected. - open var selectedDate: Date? { + open var selectedDate: Date { get { return _selectedDate } set { - if let newValue { - _selectedDate = newValue - } else { - _selectedDate = Date() - } - //update views what needed + _selectedDate = newValue setNeedsUpdate() } } @@ -75,22 +70,25 @@ open class CalendarBase: View { // /// Array of indicators for the legend. // open var indicatorData: [CalendarIndicatorModel] = [] { didSet { setNeedsUpdate() } } - - /// A callback when the selected date changes.. - open var onChangeSelectedDate: ((Date) -> String)? + + /// A callback when the date changes. Passes parameters (selectedDate). + public var onChangeSelectedDate: ((Date) -> Void)? //-------------------------------------------------- // MARK: - Private Properties //-------------------------------------------------- internal var _selectedDate: Date = Date() - private var dates: [Date] = [] - private var days: [String] = [] internal var containerSize: CGSize { CGSize(width: 328, height: 376) } //width:320/328 + private let cellItemSize = CGSize(width: 40, height: 40) private let headerHeight = 88.0 private let footerHeight = 40.0 private let items = 35 + private var selectedIndexPath : IndexPath? + private var dates: [Date] = [] + private var days: [String] = [] + internal var containerView = View().with { $0.clipsToBounds = true } @@ -163,7 +161,7 @@ open class CalendarBase: View { open override func updateView() { super.updateView() - self.fetchDates(with: selectedDate ?? Date()) + self.fetchDates(with: selectedDate) collectionView.reloadData() if hideContainerBorder { layer.borderColor = nil @@ -189,15 +187,13 @@ open class CalendarBase: View { //-------------------------------------------------- func fetchDates(with date:Date) { days.removeAll() - if let dates = selectedDate?.calendarDisplayDays { - self.dates = dates - for date in dates { - // code to be executed - if date.monthInt != selectedDate?.monthInt { - days.append("") - } else { - days.append(getDay(with: date)) - } + self.dates = selectedDate.calendarDisplayDays + for date in dates { + // code to be executed + if date.monthInt != selectedDate.monthInt { + days.append("") + } else { + days.append(getDay(with: date)) } } } @@ -234,7 +230,8 @@ extension CalendarBase: UICollectionViewDelegate, UICollectionViewDataSource, UI } } } - cell.configure(with: surface, indicators: indicators, text: days[indexPath.row], indicatorCount: indicatorCount) + cell.configure(with: surface, indicators: indicators, text: days[indexPath.row], indicatorCount: indicatorCount, selectedDate: selectedDate) + if (self.days[indexPath.row] == self.getDay(with: selectedDate)) { selectedIndexPath = indexPath } return cell } @@ -260,8 +257,16 @@ extension CalendarBase: UICollectionViewDelegate, UICollectionViewDataSource, UI } public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { -// let selectedDate = Calendar.current.date(byAdding: .day, value: 1, to: self.dates[indexPath.row])! -// print("selected day: \(days[indexPath.row]), date: \(selectedDate)") + let selectedItem = Calendar.current.date(byAdding: .day, value: 1, to: self.dates[indexPath.row])! + onChangeSelectedDate?(selectedItem) + + selectedDate = self.dates[indexPath.row] + var reloadIndexPaths = [indexPath] + + // If an cell is already selected, then it needs to be deselected. + // Add its index path to the array of index paths to be reloaded. + if let deselectIndexPath = selectedIndexPath { reloadIndexPaths.append(deselectIndexPath) } + self.collectionView.reloadItems(at: reloadIndexPaths) } public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize { diff --git a/VDS/Components/Calendar/CalendarDateCollectionViewCell.swift b/VDS/Components/Calendar/CalendarDateCollectionViewCell.swift index 68917069..eb66cde7 100644 --- a/VDS/Components/Calendar/CalendarDateCollectionViewCell.swift +++ b/VDS/Components/Calendar/CalendarDateCollectionViewCell.swift @@ -9,15 +9,20 @@ import Foundation import UIKit import VDSTokens -/// Calendar collection view cell +/// Calendar collection view for Date view final class CalendarDateCollectionViewCell: UICollectionViewCell { ///Identifier for the Calendar Date Cell static let identifier: String = String(describing: CalendarDateCollectionViewCell.self) - + //-------------------------------------------------- + // MARK: - Private Properties + //-------------------------------------------------- private lazy var dateView = DateView() + //-------------------------------------------------- + // MARK: - Initializers + //-------------------------------------------------- override init(frame: CGRect) { super.init(frame: frame) } @@ -26,17 +31,15 @@ final class CalendarDateCollectionViewCell: UICollectionViewCell { fatalError("init(coder:) has not been implemented") } - func configure(with surface: Surface, indicators: [CalendarBase.CalendarIndicatorModel], text: String, indicatorCount: Int) { + func configure(with surface: Surface, indicators: [CalendarBase.CalendarIndicatorModel], text: String, indicatorCount: Int, selectedDate: Date) { addSubview(dateView) dateView.surface = surface dateView.numberLabel.text = text dateView.indicatorCount = indicatorCount dateView.dateIndicators = indicators + dateView.selectedDate = selectedDate } - override func layoutSubviews() { - super.layoutSubviews() - } } /// Date view to show Date number and indicator if applies @@ -67,9 +70,11 @@ private class DateView : View { open var numberLabel = Label().with { $0.translatesAutoresizingMaskIntoConstraints = false $0.textAlignment = .center - $0.textStyle = .bodySmall //isCurrentDate: .boldBodySmall + $0.textStyle = .bodySmall } + open var selectedDate: Date = Date() //? { didSet { setNeedsUpdate() } } + //-------------------------------------------------- // MARK: - Private Properties //-------------------------------------------------- @@ -93,8 +98,15 @@ private class DateView : View { private lazy var shapeLayer = CAShapeLayer() - private let indicatorColorConfiguration = SurfaceColorConfiguration(VDSColor.elementsSecondaryOnlight, VDSColor.elementsSecondaryOndark) + private let selectedTextColorConfiguration = SurfaceColorConfiguration(VDSColor.elementsPrimaryInverseOnlight, VDSColor.elementsPrimaryInverseOndark) + private let selectedBackgroundColor = SurfaceColorConfiguration(VDSColor.backgroundPrimaryInverseLight, VDSColor.backgroundPrimaryInverseDark) + private let selectedCellIndicatorColorConfiguration = SurfaceColorConfiguration(VDSColor.paletteGray65, VDSColor.paletteGray44) + + private let unselectedTextColorConfiguration = SurfaceColorConfiguration(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark) + private let unselectedCellIndicatorColorConfiguration = SurfaceColorConfiguration(VDSColor.elementsSecondaryOnlight, VDSColor.elementsSecondaryOndark) + private let currentDate = Date() + //-------------------------------------------------- // MARK: - Lifecycle //-------------------------------------------------- @@ -130,17 +142,42 @@ private class DateView : View { super.updateView() numberLabel.surface = surface numberLabel.isEnabled = isEnabled - stackView.arrangedSubviews.forEach { $0.removeFromSuperview() } + + // update text color, bg color, corner radius + if numberLabel.text == self.getDay(with: selectedDate) { + numberLabel.textColor = selectedTextColorConfiguration.getColor(self) + layer.backgroundColor = selectedBackgroundColor.getColor(self).cgColor + layer.cornerRadius = VDSFormControls.borderRadius + } else { + numberLabel.textColor = unselectedTextColorConfiguration.getColor(self) + layer.backgroundColor = nil + layer.cornerRadius = 0 + } + + // add indicators if indicatorCount > 0 { let width = (indicatorCount * 8) + (Int(VDSLayout.space1X) * (indicatorCount - 1)) stackView.widthAnchor.constraint(equalToConstant: CGFloat(width)).activate() for x in (0...(dateIndicators.count-1)) { if (self.numberLabel.text == self.getDay(with: dateIndicators[x].date)) { - addIndicator(with: VDSColor.elementsSecondaryOnlight, surface: surface, clearFullCircle: (x == 1), drawSemiCircle: (x == 2)) + if numberLabel.text == self.getDay(with: selectedDate) { + addIndicator(with: selectedCellIndicatorColorConfiguration.getColor(self), surface: surface, clearFullCircle: (x == 1), drawSemiCircle: (x == 2)) + } else { + addIndicator(with: unselectedCellIndicatorColorConfiguration.getColor(self), surface: surface, clearFullCircle: (x == 1), drawSemiCircle: (x == 2)) + } } } } + + // update text style for current date + if numberLabel.text == self.getDay(with: currentDate) { + numberLabel.textStyle = .boldBodySmall + } else { + numberLabel.textStyle = .bodySmall + } + + } override open func layoutSubviews() { @@ -167,7 +204,7 @@ private class DateView : View { // update indicator indicatorView.backgroundColor = drawSemiCircle ? .clear : (clearFullCircle ? .clear : color) - indicatorView.layer.borderColor = indicatorColorConfiguration.getColor(surface).cgColor + indicatorView.layer.borderColor = color.cgColor self.layoutIfNeeded() From 7fe5123a69b0478921a782717622672d652ef939 Mon Sep 17 00:00:00 2001 From: vasavk Date: Mon, 6 May 2024 16:01:56 +0530 Subject: [PATCH 15/46] Digital ACT-191 ONEAPP-7016 story: config container background, hide/show current date indicator --- VDS/Components/Calendar/Calendar.swift | 10 +- .../CalendarDateCollectionViewCell.swift | 131 +++++------------- 2 files changed, 39 insertions(+), 102 deletions(-) diff --git a/VDS/Components/Calendar/Calendar.swift b/VDS/Components/Calendar/Calendar.swift index cfd2c11d..dd0f08eb 100644 --- a/VDS/Components/Calendar/Calendar.swift +++ b/VDS/Components/Calendar/Calendar.swift @@ -36,7 +36,7 @@ open class CalendarBase: View { open var hideContainerBorder: Bool = false { didSet { setNeedsUpdate() } } /// If set to true, the calendar will not have current date indication. - open var hideCurrentDateIndicator: Bool = false + open var hideCurrentDateIndicator: Bool = false { didSet { setNeedsUpdate() } } /// Enable specific days. Pass an array of string value in date format e.g. ['07/21/2024', '07/24/2024', 07/28/2024']. /// All other dates will be inactive @@ -117,10 +117,7 @@ open class CalendarBase: View { // MARK: - Configuration //-------------------------------------------------- internal var containerBorderColorConfiguration = SurfaceColorConfiguration(VDSColor.elementsPrimaryOnlight , VDSColor.elementsPrimaryOndark) - internal var backgroundColorConfiguration = ControlColorConfiguration().with { - $0.setSurfaceColors(VDSFormControlsColor.backgroundOnlight, VDSFormControlsColor.backgroundOndark, forState: .normal) - $0.setSurfaceColors(VDSFormControlsColor.backgroundOnlight, VDSFormControlsColor.backgroundOndark, forState: .disabled) - } + internal var backgroundColorConfiguration = SurfaceColorConfiguration(VDSFormControlsColor.backgroundOnlight, VDSFormControlsColor.backgroundOndark) //-------------------------------------------------- // MARK: - Lifecycle @@ -163,6 +160,7 @@ open class CalendarBase: View { super.updateView() self.fetchDates(with: selectedDate) collectionView.reloadData() + layer.backgroundColor = backgroundColorConfiguration.getColor(self).cgColor if hideContainerBorder { layer.borderColor = nil layer.borderWidth = 0 @@ -230,7 +228,7 @@ extension CalendarBase: UICollectionViewDelegate, UICollectionViewDataSource, UI } } } - cell.configure(with: surface, indicators: indicators, text: days[indexPath.row], indicatorCount: indicatorCount, selectedDate: selectedDate) + cell.update(with: surface, indicators: indicators, text: days[indexPath.row], indicatorCount: indicatorCount, selectedDate: selectedDate, hideDate: hideCurrentDateIndicator) if (self.days[indexPath.row] == self.getDay(with: selectedDate)) { selectedIndexPath = indexPath } return cell } diff --git a/VDS/Components/Calendar/CalendarDateCollectionViewCell.swift b/VDS/Components/Calendar/CalendarDateCollectionViewCell.swift index eb66cde7..d822c820 100644 --- a/VDS/Components/Calendar/CalendarDateCollectionViewCell.swift +++ b/VDS/Components/Calendar/CalendarDateCollectionViewCell.swift @@ -15,66 +15,6 @@ final class CalendarDateCollectionViewCell: UICollectionViewCell { ///Identifier for the Calendar Date Cell static let identifier: String = String(describing: CalendarDateCollectionViewCell.self) - //-------------------------------------------------- - // MARK: - Private Properties - //-------------------------------------------------- - private lazy var dateView = DateView() - - //-------------------------------------------------- - // MARK: - Initializers - //-------------------------------------------------- - override init(frame: CGRect) { - super.init(frame: frame) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - func configure(with surface: Surface, indicators: [CalendarBase.CalendarIndicatorModel], text: String, indicatorCount: Int, selectedDate: Date) { - addSubview(dateView) - dateView.surface = surface - dateView.numberLabel.text = text - dateView.indicatorCount = indicatorCount - dateView.dateIndicators = indicators - dateView.selectedDate = selectedDate - } - -} - -/// Date view to show Date number and indicator if applies -private class DateView : View { - - //-------------------------------------------------- - // 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) - } - - //-------------------------------------------------- - // MARK: - Public Properties - //-------------------------------------------------- - open var indicatorCount: Int = 0 - - open var dateIndicators: [CalendarBase.CalendarIndicatorModel] = [] { didSet { setNeedsUpdate() } } - - open var numberLabel = Label().with { - $0.translatesAutoresizingMaskIntoConstraints = false - $0.textAlignment = .center - $0.textStyle = .bodySmall - } - - open var selectedDate: Date = Date() //? { didSet { setNeedsUpdate() } } - //-------------------------------------------------- // MARK: - Private Properties //-------------------------------------------------- @@ -95,7 +35,13 @@ private class DateView : View { $0.setContentHuggingPriority(.defaultHigh, for: .horizontal) } }() - + + private var numberLabel = Label().with { + $0.translatesAutoresizingMaskIntoConstraints = false + $0.textAlignment = .center + $0.textStyle = .bodySmall + } + private lazy var shapeLayer = CAShapeLayer() private let selectedTextColorConfiguration = SurfaceColorConfiguration(VDSColor.elementsPrimaryInverseOnlight, VDSColor.elementsPrimaryInverseOndark) @@ -104,20 +50,25 @@ private class DateView : View { private let unselectedTextColorConfiguration = SurfaceColorConfiguration(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark) private let unselectedCellIndicatorColorConfiguration = SurfaceColorConfiguration(VDSColor.elementsSecondaryOnlight, VDSColor.elementsSecondaryOndark) - - private let currentDate = Date() + private let currentDate = Date() + //-------------------------------------------------- - // MARK: - Lifecycle + // MARK: - Initializers //-------------------------------------------------- - open override func initialSetup() { - super.initialSetup() + override init(frame: CGRect) { + super.init(frame: frame) + setUp() } - open override func setup() { - super.setup() + required init?(coder: NSCoder) { + super.init(coder: coder) + setUp() + } + + private func setUp() { isAccessibilityElement = false - addSubview(containerView) + contentView.addSubview(containerView) containerView .pinTop() .pinBottom() @@ -130,27 +81,27 @@ private class DateView : View { // Number label containerView.addSubview(numberLabel) numberLabel.pinToSuperView() - + // Indicators containerView.addSubview(stackView) let topPos = containerSize.height * 0.7 stackView.pinTop(topPos).pinBottom().pinTopGreaterThanOrEqualTo().pinTrailingLessThanOrEqualTo().pinCenterY() stackView.centerXAnchor.constraint(equalTo: centerXAnchor).activate() } - - open override func updateView() { - super.updateView() + + func update(with surface: Surface, indicators: [CalendarBase.CalendarIndicatorModel], text: String, indicatorCount: Int, selectedDate: Date, hideDate: Bool) { + numberLabel.text = text numberLabel.surface = surface - numberLabel.isEnabled = isEnabled +// numberLabel.isEnabled = isEnabled stackView.arrangedSubviews.forEach { $0.removeFromSuperview() } - + // update text color, bg color, corner radius if numberLabel.text == self.getDay(with: selectedDate) { - numberLabel.textColor = selectedTextColorConfiguration.getColor(self) - layer.backgroundColor = selectedBackgroundColor.getColor(self).cgColor + numberLabel.textColor = selectedTextColorConfiguration.getColor(surface) + layer.backgroundColor = selectedBackgroundColor.getColor(surface).cgColor layer.cornerRadius = VDSFormControls.borderRadius } else { - numberLabel.textColor = unselectedTextColorConfiguration.getColor(self) + numberLabel.textColor = unselectedTextColorConfiguration.getColor(surface) layer.backgroundColor = nil layer.cornerRadius = 0 } @@ -159,34 +110,23 @@ private class DateView : View { if indicatorCount > 0 { let width = (indicatorCount * 8) + (Int(VDSLayout.space1X) * (indicatorCount - 1)) stackView.widthAnchor.constraint(equalToConstant: CGFloat(width)).activate() - for x in (0...(dateIndicators.count-1)) { - if (self.numberLabel.text == self.getDay(with: dateIndicators[x].date)) { + for x in (0...(indicators.count-1)) { + if (self.numberLabel.text == self.getDay(with: indicators[x].date)) { if numberLabel.text == self.getDay(with: selectedDate) { - addIndicator(with: selectedCellIndicatorColorConfiguration.getColor(self), surface: surface, clearFullCircle: (x == 1), drawSemiCircle: (x == 2)) + addIndicator(with: selectedCellIndicatorColorConfiguration.getColor(surface), surface: surface, clearFullCircle: (x == 1), drawSemiCircle: (x == 2)) } else { - addIndicator(with: unselectedCellIndicatorColorConfiguration.getColor(self), surface: surface, clearFullCircle: (x == 1), drawSemiCircle: (x == 2)) + addIndicator(with: unselectedCellIndicatorColorConfiguration.getColor(surface), surface: surface, clearFullCircle: (x == 1), drawSemiCircle: (x == 2)) } } } } // update text style for current date - if numberLabel.text == self.getDay(with: currentDate) { - numberLabel.textStyle = .boldBodySmall + if (numberLabel.text == self.getDay(with: currentDate)) { + numberLabel.textStyle = hideDate ? .bodySmall : .boldBodySmall } else { numberLabel.textStyle = .bodySmall } - - - } - - override open func layoutSubviews() { - super.layoutSubviews() - } - - open override func reset() { - super.reset() - numberLabel.textStyle = .bodySmall } //-------------------------------------------------- @@ -235,5 +175,4 @@ private class DateView : View { return day } } - } From 8560d7a6d4426fe51bca1659056b16cc47194bfc Mon Sep 17 00:00:00 2001 From: vasavk Date: Tue, 7 May 2024 11:13:56 +0530 Subject: [PATCH 16/46] Digital ACT-191 ONEAPP-7016 story: handling active dates, inactive dates, min date, max date with updated values --- VDS/Components/Calendar/Calendar.swift | 32 +++------ .../CalendarDateCollectionViewCell.swift | 68 +++++++++++++------ .../Calendar/CalendarHeaderView.swift | 15 ++-- 3 files changed, 69 insertions(+), 46 deletions(-) diff --git a/VDS/Components/Calendar/Calendar.swift b/VDS/Components/Calendar/Calendar.swift index dd0f08eb..179f3efa 100644 --- a/VDS/Components/Calendar/Calendar.swift +++ b/VDS/Components/Calendar/Calendar.swift @@ -40,46 +40,36 @@ open class CalendarBase: View { /// Enable specific days. Pass an array of string value in date format e.g. ['07/21/2024', '07/24/2024', 07/28/2024']. /// All other dates will be inactive - open var activeDates: [Date] = [] - + open var activeDates: [Date] = [] { didSet { setNeedsUpdate() } } + /// Disable specific days. Pass an array of string value in date format e.g. ['07/21/2024', '07/24/2024', 07/28/2024']. /// All other dates will be active. - open var inactiveDates: [Date] = [] + open var inactiveDates: [Date] = [] { didSet { setNeedsUpdate() } } /// If provided, the calendar will allow a selection to be made from this date forward. Defaults to today. - open var minDate: Date? - + open var minDate: Date = Date() { didSet { setNeedsUpdate() } } + /// If provided, the calendar will allow a selection to be made up to this date. - open var maxDate: Date? + open var maxDate: Date = Date() { didSet { setNeedsUpdate() } } /// If provided, this is the date that will show as selected by the Calendar. /// If no value is provided, the current date will be used. If null is provided, no date will be selected. - open var selectedDate: Date { - get { return _selectedDate } - set { - _selectedDate = newValue - setNeedsUpdate() - } - } + open var selectedDate: Date = Date() { didSet { setNeedsUpdate() } } /// If provided, the calendar will be rendered with transparent background. - open var transparentBackground: Bool = false + open var transparentBackground: Bool = false { didSet { setNeedsUpdate() } } /// Array of ``CalendarIndicatorModel`` you are wanting to show on legend. open var indicators: [CalendarIndicatorModel] = [] { didSet { setNeedsUpdate() } } - -// /// Array of indicators for the legend. -// open var indicatorData: [CalendarIndicatorModel] = [] { didSet { setNeedsUpdate() } } - + /// A callback when the date changes. Passes parameters (selectedDate). public var onChangeSelectedDate: ((Date) -> Void)? //-------------------------------------------------- // MARK: - Private Properties //-------------------------------------------------- - internal var _selectedDate: Date = Date() internal var containerSize: CGSize { CGSize(width: 328, height: 376) } //width:320/328 - + private let cellItemSize = CGSize(width: 40, height: 40) private let headerHeight = 88.0 private let footerHeight = 40.0 @@ -228,7 +218,7 @@ extension CalendarBase: UICollectionViewDelegate, UICollectionViewDataSource, UI } } } - cell.update(with: surface, indicators: indicators, text: days[indexPath.row], indicatorCount: indicatorCount, selectedDate: selectedDate, hideDate: hideCurrentDateIndicator) + cell.update(with: surface, indicators: indicators, text: days[indexPath.row], indicatorCount: indicatorCount, selectedDate: selectedDate, hideDate: hideCurrentDateIndicator, minDate: minDate, maxDate: maxDate, activeDates: activeDates, inactiveDates: inactiveDates) if (self.days[indexPath.row] == self.getDay(with: selectedDate)) { selectedIndexPath = indexPath } return cell } diff --git a/VDS/Components/Calendar/CalendarDateCollectionViewCell.swift b/VDS/Components/Calendar/CalendarDateCollectionViewCell.swift index d822c820..71562de5 100644 --- a/VDS/Components/Calendar/CalendarDateCollectionViewCell.swift +++ b/VDS/Components/Calendar/CalendarDateCollectionViewCell.swift @@ -9,7 +9,7 @@ import Foundation import UIKit import VDSTokens -/// Calendar collection view for Date view +/// Calendar collection view cell for Date view final class CalendarDateCollectionViewCell: UICollectionViewCell { ///Identifier for the Calendar Date Cell @@ -35,22 +35,23 @@ final class CalendarDateCollectionViewCell: UICollectionViewCell { $0.setContentHuggingPriority(.defaultHigh, for: .horizontal) } }() - + private var numberLabel = Label().with { $0.translatesAutoresizingMaskIntoConstraints = false $0.textAlignment = .center $0.textStyle = .bodySmall } - + private lazy var shapeLayer = CAShapeLayer() private let selectedTextColorConfiguration = SurfaceColorConfiguration(VDSColor.elementsPrimaryInverseOnlight, VDSColor.elementsPrimaryInverseOndark) private let selectedBackgroundColor = SurfaceColorConfiguration(VDSColor.backgroundPrimaryInverseLight, VDSColor.backgroundPrimaryInverseDark) private let selectedCellIndicatorColorConfiguration = SurfaceColorConfiguration(VDSColor.paletteGray65, VDSColor.paletteGray44) - private let unselectedTextColorConfiguration = SurfaceColorConfiguration(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark) private let unselectedCellIndicatorColorConfiguration = SurfaceColorConfiguration(VDSColor.elementsSecondaryOnlight, VDSColor.elementsSecondaryOndark) - + private let disabledTextColorConfiguration = SurfaceColorConfiguration(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark) + private let disabledBackgroundColor = SurfaceColorConfiguration(VDSFormControlsColor.backgroundOnlight, VDSFormControlsColor.backgroundOndark) + private let currentDate = Date() //-------------------------------------------------- @@ -66,6 +67,7 @@ final class CalendarDateCollectionViewCell: UICollectionViewCell { setUp() } + /// Configuring the cell with default setup private func setUp() { isAccessibilityElement = false contentView.addSubview(containerView) @@ -81,22 +83,53 @@ final class CalendarDateCollectionViewCell: UICollectionViewCell { // Number label containerView.addSubview(numberLabel) numberLabel.pinToSuperView() - + // Indicators containerView.addSubview(stackView) let topPos = containerSize.height * 0.7 stackView.pinTop(topPos).pinBottom().pinTopGreaterThanOrEqualTo().pinTrailingLessThanOrEqualTo().pinCenterY() stackView.centerXAnchor.constraint(equalTo: centerXAnchor).activate() } - - func update(with surface: Surface, indicators: [CalendarBase.CalendarIndicatorModel], text: String, indicatorCount: Int, selectedDate: Date, hideDate: Bool) { - numberLabel.text = text + + /// Updating UI based on selected date, modified indicators data along with surface + /// Enable/disable cell based on min date, max date, active dates, inactive dates + func update(with surface: Surface, indicators: [CalendarBase.CalendarIndicatorModel], text: String, indicatorCount: Int, selectedDate: Date, hideDate: Bool, minDate: Date, maxDate: Date, activeDates: [Date], inactiveDates: [Date]) { numberLabel.surface = surface -// numberLabel.isEnabled = isEnabled + numberLabel.text = text stackView.arrangedSubviews.forEach { $0.removeFromSuperview() } - + + // disabled cells based on min date, max date. + if let day:Int = Int(numberLabel.text), day < minDate.dayInt || day > maxDate.dayInt { + numberLabel.isEnabled = false + numberLabel.textColor = disabledTextColorConfiguration.getColor(surface) + layer.backgroundColor = disabledBackgroundColor.getColor(surface).cgColor + } else { + numberLabel.isEnabled = false + // handing active dates + if activeDates.count > 0 && inactiveDates.count == 0 { + for x in (0...(activeDates.count-1)) { + if let day:Int = Int(numberLabel.text), day == activeDates[x].dayInt { + numberLabel.isEnabled = true + } + } + } else { + numberLabel.isEnabled = true + } + } + + // handling inactive dates + if inactiveDates.count > 0 { + for x in (0...(inactiveDates.count-1)) { + if let day:Int = Int(numberLabel.text), day == inactiveDates[x].dayInt { + numberLabel.isEnabled = false + numberLabel.textColor = disabledTextColorConfiguration.getColor(surface) + layer.backgroundColor = disabledBackgroundColor.getColor(surface).cgColor + } + } + } + // update text color, bg color, corner radius - if numberLabel.text == self.getDay(with: selectedDate) { + if (numberLabel.text == self.getDay(with: selectedDate)) && numberLabel.isEnabled { numberLabel.textColor = selectedTextColorConfiguration.getColor(surface) layer.backgroundColor = selectedBackgroundColor.getColor(surface).cgColor layer.cornerRadius = VDSFormControls.borderRadius @@ -112,18 +145,15 @@ final class CalendarDateCollectionViewCell: UICollectionViewCell { stackView.widthAnchor.constraint(equalToConstant: CGFloat(width)).activate() for x in (0...(indicators.count-1)) { if (self.numberLabel.text == self.getDay(with: indicators[x].date)) { - if numberLabel.text == self.getDay(with: selectedDate) { - addIndicator(with: selectedCellIndicatorColorConfiguration.getColor(surface), surface: surface, clearFullCircle: (x == 1), drawSemiCircle: (x == 2)) - } else { - addIndicator(with: unselectedCellIndicatorColorConfiguration.getColor(surface), surface: surface, clearFullCircle: (x == 1), drawSemiCircle: (x == 2)) - } + let color = (numberLabel.text == self.getDay(with: selectedDate)) ? selectedCellIndicatorColorConfiguration.getColor(surface) : unselectedCellIndicatorColorConfiguration.getColor(surface) + addIndicator(with: color, surface: surface, clearFullCircle: (x == 1), drawSemiCircle: (x == 2)) } } } // update text style for current date if (numberLabel.text == self.getDay(with: currentDate)) { - numberLabel.textStyle = hideDate ? .bodySmall : .boldBodySmall + numberLabel.textStyle = hideDate ? .bodySmall : .boldBodySmall } else { numberLabel.textStyle = .bodySmall } @@ -164,7 +194,7 @@ final class CalendarDateCollectionViewCell: UICollectionViewCell { indicatorView.layer.addSublayer(shapeLayer) } - func getDay(with date:Date) -> String { + func getDay(with date: Date) -> String { if #available(iOS 15.0, *) { return date.formatted(.dateTime.day()) } else { diff --git a/VDS/Components/Calendar/CalendarHeaderView.swift b/VDS/Components/Calendar/CalendarHeaderView.swift index f217ae40..c47f4df9 100644 --- a/VDS/Components/Calendar/CalendarHeaderView.swift +++ b/VDS/Components/Calendar/CalendarHeaderView.swift @@ -33,7 +33,7 @@ open class CalendarHeaderView: View { //-------------------------------------------------- // MARK: - Private Properties //-------------------------------------------------- - internal var containerSize: CGSize { CGSize(width: 304, height: 88) } //width:320/328 + internal var containerSize: CGSize { CGSize(width: 304, height: 88) } private lazy var stackView = UIStackView().with { $0.translatesAutoresizingMaskIntoConstraints = false @@ -60,7 +60,7 @@ open class CalendarHeaderView: View { internal var nextMonthView = View() internal var daysView = View() internal let daysOfWeek = Date.capitalizedFirstLettersOfWeekdays - + internal var previousButton = ButtonIcon().with { $0.kind = .ghost $0.iconName = .leftCaret @@ -112,7 +112,7 @@ open class CalendarHeaderView: View { super.setup() isAccessibilityElement = false - // stackView + // containerView addSubview(containerView) containerView .pinTop() @@ -127,9 +127,9 @@ open class CalendarHeaderView: View { containerView.addSubview(stackView) stackView .pinTop() + .pinBottom(VDSLayout.space1X) .pinLeading() .pinTrailing() - .pinBottom(VDSLayout.space1X) stackView.centerXAnchor.constraint(equalTo: containerView.centerXAnchor).activate() // month label view, previous and next buttons @@ -145,7 +145,9 @@ open class CalendarHeaderView: View { monthYearHeaderStackView.addArrangedSubview(previousMonthView) previousMonthView.widthAnchor.constraint(equalToConstant: 40).activate() previousMonthView.addSubview(previousButton) - + let spacing = VDSLayout.space1X + previousButton.pinTop(spacing).pinBottom(spacing).pinLeading(spacing).pinTrailing(spacing) + // month year label monthYearHeaderStackView.addArrangedSubview(monthYearLabel) @@ -153,6 +155,7 @@ open class CalendarHeaderView: View { monthYearHeaderStackView.addArrangedSubview(nextMonthView) nextMonthView.widthAnchor.constraint(equalToConstant: 40).activate() nextMonthView.addSubview(nextButton) + nextButton.pinTop(spacing).pinBottom(spacing).pinLeading(spacing).pinTrailing(spacing) // days Collection View stackView.addArrangedSubview(daysCollectionView) @@ -177,6 +180,7 @@ open class CalendarHeaderView: View { open override func reset() { super.reset() + monthYearLabel.textStyle = .boldBodySmall } } @@ -242,6 +246,5 @@ private class collectionViewCell: UICollectionViewCell { title.surface = surface title.text = text title.textColor = textColorConfiguration.getColor(surface) - title.backgroundColor = .clear } } From 8d415a72f1986b0549a3f563b0d9e0945a07ec92 Mon Sep 17 00:00:00 2001 From: vasavk Date: Wed, 8 May 2024 12:02:08 +0530 Subject: [PATCH 17/46] Digital ACT-191 ONEAPP-7958 story: Fixed issues in constraints for the component and updated to flexible width --- VDS/Components/Calendar/Calendar.swift | 14 ++- .../CalendarDateCollectionViewCell.swift | 7 +- .../Calendar/CalendarFooterView.swift | 2 +- .../Calendar/CalendarHeaderView.swift | 90 ++++++++----------- 4 files changed, 46 insertions(+), 67 deletions(-) diff --git a/VDS/Components/Calendar/Calendar.swift b/VDS/Components/Calendar/Calendar.swift index 179f3efa..f57b45be 100644 --- a/VDS/Components/Calendar/Calendar.swift +++ b/VDS/Components/Calendar/Calendar.swift @@ -68,11 +68,12 @@ open class CalendarBase: View { //-------------------------------------------------- // MARK: - Private Properties //-------------------------------------------------- - internal var containerSize: CGSize { CGSize(width: 328, height: 376) } //width:320/328 + internal var containerSize: CGSize { CGSize(width: 328, height: 376) } private let cellItemSize = CGSize(width: 40, height: 40) private let headerHeight = 88.0 private let footerHeight = 40.0 + private let calendarWidth = 304.0 private let items = 35 private var selectedIndexPath : IndexPath? @@ -127,22 +128,19 @@ open class CalendarBase: View { .pinTrailingLessThanOrEqualTo() .height(containerSize.height) .width(containerSize.width) - containerView.centerXAnchor.constraint(equalTo: centerXAnchor).activate() - - let spacing = CGFloat(VDSFormControls.spaceInset) //CGFloat(VDSLayout.space2X) // Calendar View containerView.addSubview(collectionView) + let calendarHeight = containerSize.height - (2 * VDSLayout.space4X) + let spacing = (containerSize.width - calendarWidth) / 2 collectionView .pinTop(VDSLayout.space4X) .pinBottom(VDSLayout.space4X) .pinLeading(spacing) .pinTrailing(spacing) - let width = containerSize.width - (2 * spacing) - collectionView.widthAnchor.constraint(equalToConstant: width).activate() - let height = containerSize.height - (2 * VDSLayout.space4X) - collectionView.heightAnchor.constraint(equalToConstant: height).activate() + .width(calendarWidth) + .height(calendarHeight) collectionView.centerXAnchor.constraint(equalTo: containerView.centerXAnchor).activate() } diff --git a/VDS/Components/Calendar/CalendarDateCollectionViewCell.swift b/VDS/Components/Calendar/CalendarDateCollectionViewCell.swift index 71562de5..f73a1ae1 100644 --- a/VDS/Components/Calendar/CalendarDateCollectionViewCell.swift +++ b/VDS/Components/Calendar/CalendarDateCollectionViewCell.swift @@ -33,6 +33,7 @@ final class CalendarDateCollectionViewCell: UICollectionViewCell { $0.spacing = VDSLayout.space1X $0.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal) $0.setContentHuggingPriority(.defaultHigh, for: .horizontal) + $0.backgroundColor = .clear } }() @@ -87,7 +88,7 @@ final class CalendarDateCollectionViewCell: UICollectionViewCell { // Indicators containerView.addSubview(stackView) let topPos = containerSize.height * 0.7 - stackView.pinTop(topPos).pinBottom().pinTopGreaterThanOrEqualTo().pinTrailingLessThanOrEqualTo().pinCenterY() + stackView.pinTop(topPos).pinBottom().pinTopGreaterThanOrEqualTo().pinTrailingLessThanOrEqualTo() stackView.centerXAnchor.constraint(equalTo: centerXAnchor).activate() } @@ -141,8 +142,8 @@ final class CalendarDateCollectionViewCell: UICollectionViewCell { // add indicators if indicatorCount > 0 { - let width = (indicatorCount * 8) + (Int(VDSLayout.space1X) * (indicatorCount - 1)) - stackView.widthAnchor.constraint(equalToConstant: CGFloat(width)).activate() +// let width = (indicatorCount * 8) + (Int(VDSLayout.space1X) * (indicatorCount - 1)) +// stackView.widthAnchor.constraint(equalToConstant: CGFloat(width)).activate() for x in (0...(indicators.count-1)) { if (self.numberLabel.text == self.getDay(with: indicators[x].date)) { let color = (numberLabel.text == self.getDay(with: selectedDate)) ? selectedCellIndicatorColorConfiguration.getColor(surface) : unselectedCellIndicatorColorConfiguration.getColor(surface) diff --git a/VDS/Components/Calendar/CalendarFooterView.swift b/VDS/Components/Calendar/CalendarFooterView.swift index 6574dc84..1ebd73be 100644 --- a/VDS/Components/Calendar/CalendarFooterView.swift +++ b/VDS/Components/Calendar/CalendarFooterView.swift @@ -35,7 +35,7 @@ open class CalendarFooterView: View { //-------------------------------------------------- // MARK: - Private Properties //-------------------------------------------------- - internal var containerSize: CGSize { CGSize(width: 304, height: 40) } //width:320/328 + internal var containerSize: CGSize { CGSize(width: 304, height: 40) } internal var containerView = View().with { $0.clipsToBounds = true diff --git a/VDS/Components/Calendar/CalendarHeaderView.swift b/VDS/Components/Calendar/CalendarHeaderView.swift index c47f4df9..53facda5 100644 --- a/VDS/Components/Calendar/CalendarHeaderView.swift +++ b/VDS/Components/Calendar/CalendarHeaderView.swift @@ -43,7 +43,7 @@ open class CalendarHeaderView: View { $0.backgroundColor = .clear } - private lazy var monthYearHeaderStackView = UIStackView().with { + private lazy var topHeaderView = UIStackView().with { $0.translatesAutoresizingMaskIntoConstraints = false $0.distribution = .fill $0.spacing = VDSLayout.space1X @@ -51,15 +51,22 @@ open class CalendarHeaderView: View { $0.backgroundColor = .clear } - internal var containerView = View().with { - $0.clipsToBounds = true - $0.backgroundColor = .clear - } - internal var monthView = View() + private lazy var daysCollectionView: UICollectionView = { + let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout()) + collectionView.isScrollEnabled = false + collectionView.translatesAutoresizingMaskIntoConstraints = false + collectionView.delegate = self + collectionView.dataSource = self + collectionView.showsHorizontalScrollIndicator = false + collectionView.showsVerticalScrollIndicator = false + collectionView.backgroundColor = .clear + collectionView.register(collectionViewCell.self, forCellWithReuseIdentifier: collectionViewCell.identifier) + return collectionView + }() + internal var previousMonthView = View() internal var nextMonthView = View() - internal var daysView = View() - internal let daysOfWeek = Date.capitalizedFirstLettersOfWeekdays + let viewSize = 40.0 internal var previousButton = ButtonIcon().with { $0.kind = .ghost @@ -86,20 +93,8 @@ open class CalendarHeaderView: View { $0.isAccessibilityElement = false } - private lazy var daysCollectionView: UICollectionView = { - let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout()) - collectionView.isScrollEnabled = false - collectionView.translatesAutoresizingMaskIntoConstraints = false - collectionView.delegate = self - collectionView.dataSource = self - collectionView.showsHorizontalScrollIndicator = false - collectionView.showsVerticalScrollIndicator = false - collectionView.backgroundColor = .clear - collectionView.register(collectionViewCell.self, forCellWithReuseIdentifier: collectionViewCell.identifier) - return collectionView - }() - - internal var monthYearLabelTextColorConfiguration = SurfaceColorConfiguration(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark) + internal let daysOfWeek = Date.capitalizedFirstLettersOfWeekdays + internal let monthYearLabelTextColorConfiguration = SurfaceColorConfiguration(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark) //-------------------------------------------------- // MARK: - Lifecycle @@ -112,56 +107,41 @@ open class CalendarHeaderView: View { super.setup() isAccessibilityElement = false - // containerView - addSubview(containerView) - containerView - .pinTop() - .pinBottom() - .pinLeadingGreaterThanOrEqualTo() - .pinTrailingLessThanOrEqualTo() - .height(containerSize.height) - .width(containerSize.width) - containerView.centerXAnchor.constraint(equalTo: centerXAnchor).activate() - // stackview - containerView.addSubview(stackView) + addSubview(stackView) stackView .pinTop() .pinBottom(VDSLayout.space1X) .pinLeading() .pinTrailing() - stackView.centerXAnchor.constraint(equalTo: containerView.centerXAnchor).activate() + .height(containerSize.height) + .width(containerSize.width) + stackView.centerXAnchor.constraint(equalTo: centerXAnchor).activate() - // month label view, previous and next buttons - stackView.addArrangedSubview(monthView) - monthView.backgroundColor = .clear - monthView.heightAnchor.constraint(equalToConstant: 40).isActive = true - - // month Header stack view - monthView.addSubview(monthYearHeaderStackView) - monthYearHeaderStackView.pinToSuperView() + // top header stack view + stackView.addArrangedSubview(topHeaderView) + topHeaderView.heightAnchor.constraint(equalToConstant: viewSize).isActive = true + topHeaderView.centerXAnchor.constraint(equalTo: centerXAnchor).activate() // previous button - monthYearHeaderStackView.addArrangedSubview(previousMonthView) - previousMonthView.widthAnchor.constraint(equalToConstant: 40).activate() + topHeaderView.addArrangedSubview(previousMonthView) + previousMonthView.widthAnchor.constraint(equalToConstant: viewSize).activate() previousMonthView.addSubview(previousButton) - let spacing = VDSLayout.space1X - previousButton.pinTop(spacing).pinBottom(spacing).pinLeading(spacing).pinTrailing(spacing) + previousButton.pinCenterY().pinCenterX() // month year label - monthYearHeaderStackView.addArrangedSubview(monthYearLabel) + topHeaderView.addArrangedSubview(monthYearLabel) // next button - monthYearHeaderStackView.addArrangedSubview(nextMonthView) - nextMonthView.widthAnchor.constraint(equalToConstant: 40).activate() + topHeaderView.addArrangedSubview(nextMonthView) + nextMonthView.widthAnchor.constraint(equalToConstant: viewSize).activate() nextMonthView.addSubview(nextButton) - nextButton.pinTop(spacing).pinBottom(spacing).pinLeading(spacing).pinTrailing(spacing) + nextButton.pinCenterY().pinCenterX() // days Collection View stackView.addArrangedSubview(daysCollectionView) - daysCollectionView.widthAnchor.constraint(equalTo: widthAnchor).activate() - daysCollectionView.heightAnchor.constraint(equalTo: monthView.heightAnchor).activate() - daysCollectionView.centerXAnchor.constraint(equalTo: containerView.centerXAnchor).activate() + topHeaderView.heightAnchor.constraint(equalToConstant: viewSize).isActive = true + daysCollectionView.centerXAnchor.constraint(equalTo: centerXAnchor).activate() } open override func updateView() { @@ -197,7 +177,7 @@ extension CalendarHeaderView: UICollectionViewDelegate, UICollectionViewDataSour } public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { - return CGSize(width: 40, height: 40) + return CGSize(width: viewSize, height: viewSize) } public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat { From 80f90e0b56c735defb095c849a46c1c83264f510 Mon Sep 17 00:00:00 2001 From: vasavk Date: Wed, 8 May 2024 12:03:57 +0530 Subject: [PATCH 18/46] Digital ACT-191 ONEAPP-7958 story: added missing date function --- VDS/Components/Calendar/Date+Extension.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/VDS/Components/Calendar/Date+Extension.swift b/VDS/Components/Calendar/Date+Extension.swift index 07284507..13e80479 100644 --- a/VDS/Components/Calendar/Date+Extension.swift +++ b/VDS/Components/Calendar/Date+Extension.swift @@ -62,4 +62,7 @@ extension Date { Calendar.current.component(.month, from: self) } + var dayInt: Int { + Calendar.current.component(.day, from: self) + } } From 450dd1aae8f6847e108c2da1174952e6e4def50d Mon Sep 17 00:00:00 2001 From: vasavk Date: Thu, 9 May 2024 09:43:13 +0530 Subject: [PATCH 19/46] Digital ACT-191 ONEAPP-7958 story: added action for next and previous on changing min / max dates --- VDS/Components/Calendar/Calendar.swift | 52 +++++++++-- .../CalendarDateCollectionViewCell.swift | 22 ++--- .../Calendar/CalendarHeaderView.swift | 87 ++++++++++--------- .../Calendar/CalendarReusableView.swift | 22 ----- VDS/Components/Calendar/Date+Extension.swift | 36 ++++++++ 5 files changed, 139 insertions(+), 80 deletions(-) diff --git a/VDS/Components/Calendar/Calendar.swift b/VDS/Components/Calendar/Calendar.swift index f57b45be..ce183fac 100644 --- a/VDS/Components/Calendar/Calendar.swift +++ b/VDS/Components/Calendar/Calendar.swift @@ -79,7 +79,8 @@ open class CalendarBase: View { private var selectedIndexPath : IndexPath? private var dates: [Date] = [] private var days: [String] = [] - + private var displayDate: Date = Date() + internal var containerView = View().with { $0.clipsToBounds = true } @@ -146,8 +147,14 @@ open class CalendarBase: View { open override func updateView() { super.updateView() - self.fetchDates(with: selectedDate) - collectionView.reloadData() + // range check between min & max dates + if (minDate <= maxDate) { + // Check if current date falls between min & max dates. + let fallsBetween = displayDate.isBetweeen(date: minDate, andDate: maxDate) + displayDate = fallsBetween ? displayDate : minDate + self.fetchDates(with: displayDate) + collectionView.reloadData() + } layer.backgroundColor = backgroundColorConfiguration.getColor(self).cgColor if hideContainerBorder { layer.borderColor = nil @@ -171,12 +178,12 @@ open class CalendarBase: View { //-------------------------------------------------- // MARK: - Private Methods //-------------------------------------------------- - func fetchDates(with date:Date) { + func fetchDates(with aDate:Date) { days.removeAll() - self.dates = selectedDate.calendarDisplayDays + self.dates = aDate.calendarDisplayDays for date in dates { // code to be executed - if date.monthInt != selectedDate.monthInt { + if date.monthInt != aDate.monthInt { days.append("") } else { days.append(getDay(with: date)) @@ -216,7 +223,7 @@ extension CalendarBase: UICollectionViewDelegate, UICollectionViewDataSource, UI } } } - cell.update(with: surface, indicators: indicators, text: days[indexPath.row], indicatorCount: indicatorCount, selectedDate: selectedDate, hideDate: hideCurrentDateIndicator, minDate: minDate, maxDate: maxDate, activeDates: activeDates, inactiveDates: inactiveDates) + cell.update(with: surface, indicators: indicators, text: days[indexPath.row], indicatorCount: indicatorCount, selectedDate: selectedDate, displayDate: displayDate, hideDate: hideCurrentDateIndicator, minDate: minDate, maxDate: maxDate, activeDates: activeDates, inactiveDates: inactiveDates) if (self.days[indexPath.row] == self.getDay(with: selectedDate)) { selectedIndexPath = indexPath } return cell } @@ -227,7 +234,36 @@ extension CalendarBase: UICollectionViewDelegate, UICollectionViewDataSource, UI guard let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: CalendarHeaderReusableView.identifier, for: indexPath) as? CalendarHeaderReusableView else { return UICollectionReusableView() } - header.configure(with: surface) + var nextEnabled = false + var prevEnabled = false + + // check the interval between min date, max date.. set enable/disable flag for next / previous buttons. + if ((displayDate.monthInt < maxDate.monthInt) && (displayDate.yearInt == maxDate.yearInt)) || (displayDate.yearInt < maxDate.yearInt) { + nextEnabled = true + } + if ((minDate.monthInt < displayDate.monthInt) && (minDate.yearInt == displayDate.yearInt)) || (minDate.yearInt < displayDate.yearInt) { + prevEnabled = true + } + + header.nextClicked = { [weak self] in + guard let self = self else { return } + let aDate = Calendar.current.date(byAdding: .month, value: 1, to:self.displayDate)! + if ((aDate.monthInt <= maxDate.monthInt) && (aDate.yearInt == maxDate.yearInt)) || (aDate.yearInt < maxDate.yearInt) { + displayDate = aDate + self.fetchDates(with: displayDate) + self.collectionView.reloadData() + } + } + header.previousClicked = { [weak self] in + guard let self = self else { return } + let aDate = Calendar.current.date(byAdding: .month, value: -1, to:self.displayDate)! + if ((minDate.monthInt <= aDate.monthInt) && (minDate.yearInt == aDate.yearInt)) || (minDate.yearInt < aDate.yearInt) { + displayDate = aDate + self.fetchDates(with: displayDate) + self.collectionView.reloadData() + } + } + header.update(with: surface, aDate: displayDate, nextEnabled: nextEnabled, previousEnabled: prevEnabled) return header } else { // Footer diff --git a/VDS/Components/Calendar/CalendarDateCollectionViewCell.swift b/VDS/Components/Calendar/CalendarDateCollectionViewCell.swift index f73a1ae1..efc506d8 100644 --- a/VDS/Components/Calendar/CalendarDateCollectionViewCell.swift +++ b/VDS/Components/Calendar/CalendarDateCollectionViewCell.swift @@ -94,12 +94,12 @@ final class CalendarDateCollectionViewCell: UICollectionViewCell { /// Updating UI based on selected date, modified indicators data along with surface /// Enable/disable cell based on min date, max date, active dates, inactive dates - func update(with surface: Surface, indicators: [CalendarBase.CalendarIndicatorModel], text: String, indicatorCount: Int, selectedDate: Date, hideDate: Bool, minDate: Date, maxDate: Date, activeDates: [Date], inactiveDates: [Date]) { + func update(with surface: Surface, indicators: [CalendarBase.CalendarIndicatorModel], text: String, indicatorCount: Int, selectedDate: Date, displayDate: Date, hideDate: Bool, minDate: Date, maxDate: Date, activeDates: [Date], inactiveDates: [Date]) { numberLabel.surface = surface numberLabel.text = text stackView.arrangedSubviews.forEach { $0.removeFromSuperview() } - - // disabled cells based on min date, max date. + + // enable/disable cells based on min date, max date. if let day:Int = Int(numberLabel.text), day < minDate.dayInt || day > maxDate.dayInt { numberLabel.isEnabled = false numberLabel.textColor = disabledTextColorConfiguration.getColor(surface) @@ -121,16 +121,18 @@ final class CalendarDateCollectionViewCell: UICollectionViewCell { // handling inactive dates if inactiveDates.count > 0 { for x in (0...(inactiveDates.count-1)) { - if let day:Int = Int(numberLabel.text), day == inactiveDates[x].dayInt { - numberLabel.isEnabled = false - numberLabel.textColor = disabledTextColorConfiguration.getColor(surface) - layer.backgroundColor = disabledBackgroundColor.getColor(surface).cgColor + if (activeDates[x].monthInt == displayDate.monthInt) && (activeDates[x].yearInt == displayDate.yearInt) { + if let day:Int = Int(numberLabel.text), day == inactiveDates[x].dayInt { + numberLabel.isEnabled = false + numberLabel.textColor = disabledTextColorConfiguration.getColor(surface) + layer.backgroundColor = disabledBackgroundColor.getColor(surface).cgColor + } } } } // update text color, bg color, corner radius - if (numberLabel.text == self.getDay(with: selectedDate)) && numberLabel.isEnabled { + if (numberLabel.text == self.getDay(with: selectedDate)) && (selectedDate.monthInt == displayDate.monthInt) && (selectedDate.yearInt == displayDate.yearInt) && numberLabel.isEnabled { numberLabel.textColor = selectedTextColorConfiguration.getColor(surface) layer.backgroundColor = selectedBackgroundColor.getColor(surface).cgColor layer.cornerRadius = VDSFormControls.borderRadius @@ -142,8 +144,6 @@ final class CalendarDateCollectionViewCell: UICollectionViewCell { // add indicators if indicatorCount > 0 { -// let width = (indicatorCount * 8) + (Int(VDSLayout.space1X) * (indicatorCount - 1)) -// stackView.widthAnchor.constraint(equalToConstant: CGFloat(width)).activate() for x in (0...(indicators.count-1)) { if (self.numberLabel.text == self.getDay(with: indicators[x].date)) { let color = (numberLabel.text == self.getDay(with: selectedDate)) ? selectedCellIndicatorColorConfiguration.getColor(surface) : unselectedCellIndicatorColorConfiguration.getColor(surface) @@ -153,7 +153,7 @@ final class CalendarDateCollectionViewCell: UICollectionViewCell { } // update text style for current date - if (numberLabel.text == self.getDay(with: currentDate)) { + if (numberLabel.text == self.getDay(with: currentDate)) && (currentDate.monthInt == displayDate.monthInt) { numberLabel.textStyle = hideDate ? .bodySmall : .boldBodySmall } else { numberLabel.textStyle = .bodySmall diff --git a/VDS/Components/Calendar/CalendarHeaderView.swift b/VDS/Components/Calendar/CalendarHeaderView.swift index 53facda5..cdb08a18 100644 --- a/VDS/Components/Calendar/CalendarHeaderView.swift +++ b/VDS/Components/Calendar/CalendarHeaderView.swift @@ -10,25 +10,19 @@ import UIKit import VDSTokens /// Header view to display month and year along with days of week -open class CalendarHeaderView: View { - //-------------------------------------------------- - // MARK: - Initializers - //-------------------------------------------------- - required public init() { - super.init(frame: .zero) - } +class CalendarHeaderReusableView: UICollectionReusableView { - public override init(frame: CGRect) { - super.init(frame: .zero) - } - - public required init?(coder: NSCoder) { - super.init(coder: coder) - } + ///Identifier for the Calendar Header Reusable View + static let identifier: String = String(describing: CalendarHeaderReusableView.self) //-------------------------------------------------- // MARK: - Public Properties //-------------------------------------------------- + /// A callback when the next button clicked + public var nextClicked: (() -> (Void))? + + /// A callback when the previous button clicked + public var previousClicked: (() -> (Void))? //-------------------------------------------------- // MARK: - Private Properties @@ -64,6 +58,9 @@ open class CalendarHeaderView: View { return collectionView }() + private var surface: Surface = .light + private var displayDate: Date = Date() + internal var previousMonthView = View() internal var nextMonthView = View() let viewSize = 40.0 @@ -84,7 +81,7 @@ open class CalendarHeaderView: View { $0.size = .small } - internal var monthYearLabel = Label().with { + internal var headerTitle = Label().with { $0.translatesAutoresizingMaskIntoConstraints = false $0.textAlignment = .center $0.numberOfLines = 1 @@ -94,17 +91,23 @@ open class CalendarHeaderView: View { } internal let daysOfWeek = Date.capitalizedFirstLettersOfWeekdays - internal let monthYearLabelTextColorConfiguration = SurfaceColorConfiguration(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark) - + internal let headerTitleTextColorConfiguration = SurfaceColorConfiguration(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark) + //-------------------------------------------------- - // MARK: - Lifecycle + // MARK: - Initializers //-------------------------------------------------- - open override func initialSetup() { - super.initialSetup() + override init(frame: CGRect) { + super.init(frame: frame) + setUp() } - open override func setup() { - super.setup() + required init?(coder: NSCoder) { + super.init(coder: coder) + setUp() + } + + /// Configuring the cell with default setup + private func setUp() { isAccessibilityElement = false // stackview @@ -114,7 +117,7 @@ open class CalendarHeaderView: View { .pinBottom(VDSLayout.space1X) .pinLeading() .pinTrailing() - .height(containerSize.height) + .height(containerSize.height - VDSLayout.space1X) .width(containerSize.width) stackView.centerXAnchor.constraint(equalTo: centerXAnchor).activate() @@ -128,43 +131,49 @@ open class CalendarHeaderView: View { previousMonthView.widthAnchor.constraint(equalToConstant: viewSize).activate() previousMonthView.addSubview(previousButton) previousButton.pinCenterY().pinCenterX() - + previousButton.onClick = { _ in self.previousButtonClick() } + // month year label - topHeaderView.addArrangedSubview(monthYearLabel) + topHeaderView.addArrangedSubview(headerTitle) // next button topHeaderView.addArrangedSubview(nextMonthView) nextMonthView.widthAnchor.constraint(equalToConstant: viewSize).activate() nextMonthView.addSubview(nextButton) nextButton.pinCenterY().pinCenterX() - + nextButton.onClick = { _ in self.nextButtonClick() } + // days Collection View stackView.addArrangedSubview(daysCollectionView) topHeaderView.heightAnchor.constraint(equalToConstant: viewSize).isActive = true daysCollectionView.centerXAnchor.constraint(equalTo: centerXAnchor).activate() } - open override func updateView() { - super.updateView() - monthYearLabel.surface = surface + /// Updating UI based on next/previous clicks along with surface. + /// Updating UI to enable/disable the next & previous buttons, updating header title + func update(with surface: Surface, aDate: Date, nextEnabled: Bool, previousEnabled: Bool) { + self.surface = surface + headerTitle.surface = surface previousButton.surface = surface nextButton.surface = surface + nextButton.isEnabled = nextEnabled + previousButton.isEnabled = previousEnabled daysCollectionView.reloadData() - monthYearLabel.text = "May 2024" - monthYearLabel.textColor = monthYearLabelTextColorConfiguration.getColor(surface) + let labelText = aDate.getMonthName(date: aDate) + " \(aDate.yearInt)" + headerTitle.text = labelText + headerTitle.textColor = headerTitleTextColorConfiguration.getColor(surface) } - override open func layoutSubviews() { - super.layoutSubviews() + func nextButtonClick() { + nextClicked?() } - - open override func reset() { - super.reset() - monthYearLabel.textStyle = .boldBodySmall + + func previousButtonClick() { + previousClicked?() } } -extension CalendarHeaderView: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { +extension CalendarHeaderReusableView: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return daysOfWeek.count @@ -172,7 +181,7 @@ extension CalendarHeaderView: UICollectionViewDelegate, UICollectionViewDataSour public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: collectionViewCell.identifier, for: indexPath) as? collectionViewCell else { return UICollectionViewCell() } - cell.updateTitle(text: daysOfWeek[indexPath.row], surface: surface) + cell.updateTitle(text: daysOfWeek[indexPath.row], surface: self.surface) return cell } diff --git a/VDS/Components/Calendar/CalendarReusableView.swift b/VDS/Components/Calendar/CalendarReusableView.swift index ebb2d269..4a695ab0 100644 --- a/VDS/Components/Calendar/CalendarReusableView.swift +++ b/VDS/Components/Calendar/CalendarReusableView.swift @@ -8,28 +8,6 @@ import UIKit import VDSTokens -/// Custom header view -class CalendarHeaderReusableView: UICollectionReusableView { - - ///Identifier for the Calendar Header Reusable View - static let identifier: String = String(describing: CalendarHeaderReusableView.self) - - private lazy var headerView = CalendarHeaderView() - - override init(frame: CGRect) { - super.init(frame: frame) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - func configure(with surface: Surface) { - headerView.surface = surface - addSubview(headerView) - } -} - /// Custom footer view class CalendarFooterReusableView: UICollectionReusableView { diff --git a/VDS/Components/Calendar/Date+Extension.swift b/VDS/Components/Calendar/Date+Extension.swift index 13e80479..2a016ecb 100644 --- a/VDS/Components/Calendar/Date+Extension.swift +++ b/VDS/Components/Calendar/Date+Extension.swift @@ -8,7 +8,10 @@ import Foundation extension Date { + static var firstDayOfWeek = Calendar.current.firstWeekday + + /// Capitalizes the first letter of the day of the week static var capitalizedFirstLettersOfWeekdays: [String] { let calendar = Calendar.current let weekdays = calendar.shortWeekdaySymbols @@ -19,6 +22,18 @@ extension Date { } } + /// Returns all month names + static var fullMonthNames: [String] { + let dateFormatter = DateFormatter() + dateFormatter.locale = Locale.current + + return (1...12).compactMap { month in + dateFormatter.setLocalizedDateFormatFromTemplate("MMMM") + let date = Calendar.current.date(from: DateComponents(year: 2000, month: month, day: 1)) + return date.map { dateFormatter.string(from: $0) } + } + } + var startOfMonth: Date { Calendar.current.dateInterval(of: .month, for: self)!.start } @@ -28,6 +43,7 @@ extension Date { return Calendar.current.date(byAdding: .day, value: -1, to: lastDay)! } + /// Get the number of days of the month var numberOfDaysInMonth: Int { Calendar.current.component(.day, from: endOfMonth) } @@ -41,6 +57,7 @@ extension Date { return Calendar.current.date(byAdding: .day, value: -numberFromPreviousMonth, to: startOfMonth)! } + /// Get the days of the month to display var calendarDisplayDays: [Date] { var days: [Date] = [] // Start with days from the previous month to fill the grid @@ -58,11 +75,30 @@ extension Date { return days } + /// Returns the year value of the given date + var yearInt: Int { + Calendar.current.component(.year, from: self) + } + + /// Returns the month value of the given date var monthInt: Int { Calendar.current.component(.month, from: self) } + /// Returns the day value of the given date var dayInt: Int { Calendar.current.component(.day, from: self) } + + /// Check if the date falls between the given dates + func isBetweeen(date date1: Date, andDate date2: Date) -> Bool { + return date1.compare(self) == self.compare(date2) + } + + /// Returns the month name of the given date + func getMonthName(date: Date) -> String { + let names = Date.fullMonthNames + return names[date.monthInt - 1] + } + } From 4b5c4a28b25cbf6b29b7004322d397164d29b735 Mon Sep 17 00:00:00 2001 From: vasavk Date: Thu, 9 May 2024 13:38:02 +0530 Subject: [PATCH 20/46] Digital ACT-191 ONEAPP-7958 story: handling days of month based on min, max date and active, inactive dates selected. --- VDS/Components/Calendar/Calendar.swift | 7 +- .../CalendarDateCollectionViewCell.swift | 128 +++++++++++++----- 2 files changed, 102 insertions(+), 33 deletions(-) diff --git a/VDS/Components/Calendar/Calendar.swift b/VDS/Components/Calendar/Calendar.swift index ce183fac..e2f101d4 100644 --- a/VDS/Components/Calendar/Calendar.swift +++ b/VDS/Components/Calendar/Calendar.swift @@ -39,7 +39,7 @@ open class CalendarBase: View { open var hideCurrentDateIndicator: Bool = false { didSet { setNeedsUpdate() } } /// Enable specific days. Pass an array of string value in date format e.g. ['07/21/2024', '07/24/2024', 07/28/2024']. - /// All other dates will be inactive + /// All other dates will be inactive. open var activeDates: [Date] = [] { didSet { setNeedsUpdate() } } /// Disable specific days. Pass an array of string value in date format e.g. ['07/21/2024', '07/24/2024', 07/28/2024']. @@ -112,12 +112,13 @@ open class CalendarBase: View { internal var backgroundColorConfiguration = SurfaceColorConfiguration(VDSFormControlsColor.backgroundOnlight, VDSFormControlsColor.backgroundOndark) //-------------------------------------------------- - // MARK: - Lifecycle + // MARK: - Overrides //-------------------------------------------------- open override func initialSetup() { super.initialSetup() } + /// Called once when a view is initialized and is used to Setup additional UI or other constants and configurations. open override func setup() { super.setup() isAccessibilityElement = false @@ -171,6 +172,7 @@ open class CalendarBase: View { super.layoutSubviews() } + /// Resets to default settings. open override func reset() { super.reset() } @@ -283,6 +285,7 @@ extension CalendarBase: UICollectionViewDelegate, UICollectionViewDataSource, UI onChangeSelectedDate?(selectedItem) selectedDate = self.dates[indexPath.row] + displayDate = selectedDate var reloadIndexPaths = [indexPath] // If an cell is already selected, then it needs to be deselected. diff --git a/VDS/Components/Calendar/CalendarDateCollectionViewCell.swift b/VDS/Components/Calendar/CalendarDateCollectionViewCell.swift index efc506d8..40ba8b82 100644 --- a/VDS/Components/Calendar/CalendarDateCollectionViewCell.swift +++ b/VDS/Components/Calendar/CalendarDateCollectionViewCell.swift @@ -68,6 +68,10 @@ final class CalendarDateCollectionViewCell: UICollectionViewCell { setUp() } + //-------------------------------------------------- + // MARK: - Private Methods + //-------------------------------------------------- + /// Configuring the cell with default setup private func setUp() { isAccessibilityElement = false @@ -95,37 +99,20 @@ final class CalendarDateCollectionViewCell: UICollectionViewCell { /// Updating UI based on selected date, modified indicators data along with surface /// Enable/disable cell based on min date, max date, active dates, inactive dates func update(with surface: Surface, indicators: [CalendarBase.CalendarIndicatorModel], text: String, indicatorCount: Int, selectedDate: Date, displayDate: Date, hideDate: Bool, minDate: Date, maxDate: Date, activeDates: [Date], inactiveDates: [Date]) { + + stackView.arrangedSubviews.forEach { $0.removeFromSuperview() } numberLabel.surface = surface numberLabel.text = text - stackView.arrangedSubviews.forEach { $0.removeFromSuperview() } - - // enable/disable cells based on min date, max date. - if let day:Int = Int(numberLabel.text), day < minDate.dayInt || day > maxDate.dayInt { - numberLabel.isEnabled = false - numberLabel.textColor = disabledTextColorConfiguration.getColor(surface) - layer.backgroundColor = disabledBackgroundColor.getColor(surface).cgColor - } else { - numberLabel.isEnabled = false - // handing active dates - if activeDates.count > 0 && inactiveDates.count == 0 { - for x in (0...(activeDates.count-1)) { - if let day:Int = Int(numberLabel.text), day == activeDates[x].dayInt { - numberLabel.isEnabled = true - } - } - } else { - numberLabel.isEnabled = true - } - } + + // enable/disable cells based on min date, max date and active/inactive dates + self.updateLabel(with:surface, displayDate: displayDate, minDate: minDate, maxDate: maxDate, activeDates: activeDates, inactiveDates: inactiveDates) // handling inactive dates if inactiveDates.count > 0 { for x in (0...(inactiveDates.count-1)) { - if (activeDates[x].monthInt == displayDate.monthInt) && (activeDates[x].yearInt == displayDate.yearInt) { + if (inactiveDates[x].monthInt == displayDate.monthInt) && (inactiveDates[x].yearInt == displayDate.yearInt) { if let day:Int = Int(numberLabel.text), day == inactiveDates[x].dayInt { - numberLabel.isEnabled = false - numberLabel.textColor = disabledTextColorConfiguration.getColor(surface) - layer.backgroundColor = disabledBackgroundColor.getColor(surface).cgColor + disableLabel(with: surface) } } } @@ -145,24 +132,103 @@ final class CalendarDateCollectionViewCell: UICollectionViewCell { // add indicators if indicatorCount > 0 { for x in (0...(indicators.count-1)) { - if (self.numberLabel.text == self.getDay(with: indicators[x].date)) { - let color = (numberLabel.text == self.getDay(with: selectedDate)) ? selectedCellIndicatorColorConfiguration.getColor(surface) : unselectedCellIndicatorColorConfiguration.getColor(surface) - addIndicator(with: color, surface: surface, clearFullCircle: (x == 1), drawSemiCircle: (x == 2)) + // irrespective of month and year, if it needs to show indicators on every month - comment below first if condition + if (indicators[x].date.monthInt == displayDate.monthInt) && (indicators[x].date.yearInt == displayDate.yearInt) { + if (self.numberLabel.text == self.getDay(with: indicators[x].date)) { + let color = (numberLabel.text == self.getDay(with: selectedDate)) ? selectedCellIndicatorColorConfiguration.getColor(surface) : unselectedCellIndicatorColorConfiguration.getColor(surface) + addIndicator(with: color, surface: surface, clearFullCircle: (x == 1), drawSemiCircle: (x == 2)) + } } } } // update text style for current date - if (numberLabel.text == self.getDay(with: currentDate)) && (currentDate.monthInt == displayDate.monthInt) { + if (numberLabel.text == self.getDay(with: currentDate)) && (currentDate.monthInt == displayDate.monthInt) && (currentDate.yearInt == displayDate.yearInt) { numberLabel.textStyle = hideDate ? .bodySmall : .boldBodySmall } else { numberLabel.textStyle = .bodySmall } } - //-------------------------------------------------- - // MARK: - Private Methods - //-------------------------------------------------- + func disableLabel(with surface: Surface) { + numberLabel.isEnabled = false + numberLabel.textColor = disabledTextColorConfiguration.getColor(surface) + layer.backgroundColor = disabledBackgroundColor.getColor(surface).cgColor + } + + func showActiveDates(with displayDate: Date, activeDates: [Date], inactiveDates: [Date]) { + for x in (0...(activeDates.count-1)) { + if (activeDates[x].monthInt == displayDate.monthInt) && (activeDates[x].yearInt == displayDate.yearInt ) { + if let day:Int = Int(numberLabel.text), day == activeDates[x].dayInt { + numberLabel.isEnabled = true + } + } + } + } + + // handing active dates if exist, else enable numberLabel to display day + func handleActiveDates(with displayDate: Date, activeDates: [Date], inactiveDates: [Date]) { + if activeDates.count > 0 && inactiveDates.count == 0 { + showActiveDates(with: displayDate, activeDates: activeDates, inactiveDates: inactiveDates) + } else { + numberLabel.isEnabled = true + } + } + + // enable all days if no active dates, handing active dates if exist + func enableAllDaysAndCheckActiveDates(with surface:Surface, displayDate: Date, activeDates: [Date], inactiveDates: [Date]) { + if activeDates.count > 0 && inactiveDates.count == 0 { + disableLabel(with: surface) + showActiveDates(with: displayDate, activeDates: activeDates, inactiveDates: inactiveDates) + } else { + numberLabel.isEnabled = true + } + } + + // enable/disable cells based on min date, max date and active/inactive dates + func updateLabel(with surface: Surface, displayDate: Date, minDate: Date, maxDate: Date, activeDates: [Date], inactiveDates: [Date]) { + if (minDate.yearInt == displayDate.yearInt) || (maxDate.yearInt == displayDate.yearInt) { + if (minDate.monthInt == displayDate.monthInt) && (maxDate.monthInt == displayDate.monthInt) { + // validate days to enable/disable + if let day:Int = Int(numberLabel.text), day < minDate.dayInt || day > maxDate.dayInt { + disableLabel(with: surface) + } else { + numberLabel.isEnabled = false + handleActiveDates(with: displayDate, activeDates: activeDates, inactiveDates: inactiveDates) + } + } else if (minDate.monthInt == displayDate.monthInt) || (maxDate.monthInt == displayDate.monthInt) { + if (minDate.monthInt == displayDate.monthInt) { + // validate days to enable/disable + if let day:Int = Int(numberLabel.text), day < minDate.dayInt { + disableLabel(with: surface) + } else { + numberLabel.isEnabled = false + handleActiveDates(with: displayDate, activeDates: activeDates, inactiveDates: inactiveDates) + } + + } else if (maxDate.monthInt == displayDate.monthInt) { + // validate days to enable/disable + if let day:Int = Int(numberLabel.text), day > maxDate.dayInt { + disableLabel(with: surface) + } else { + numberLabel.isEnabled = false + handleActiveDates(with: displayDate, activeDates: activeDates, inactiveDates: inactiveDates) + } + + } + } else if ((minDate.monthInt < displayDate.monthInt) || (displayDate.monthInt < maxDate.monthInt)) { + // enable all days if no active dates + // handing active dates + enableAllDaysAndCheckActiveDates(with: surface, displayDate: displayDate, activeDates: activeDates, inactiveDates: inactiveDates) + } + } else { + // enable all days if no active dates + // handing active dates + enableAllDaysAndCheckActiveDates(with: surface, displayDate: displayDate, activeDates: activeDates, inactiveDates: inactiveDates) + } + + } + func addIndicator(with color: UIColor, surface: Surface, clearFullCircle: Bool, drawSemiCircle: Bool) { // add indicator let indicatorView: View = View().with { From 6bb6d31b39687be091d35f4b72b9c8b1933c73a1 Mon Sep 17 00:00:00 2001 From: vasavk Date: Thu, 9 May 2024 20:52:46 +0530 Subject: [PATCH 21/46] Digital ACT-191 ONEAPP-7958 story: code refactored --- VDS.xcodeproj/project.pbxproj | 28 ++-- VDS/Components/Calendar/Calendar.swift | 12 +- ...wCell.swift => CalendarDateViewCell.swift} | 133 +++++++++++------- ...swift => CalendarFooterReusableView.swift} | 84 +++++------ ...swift => CalendarHeaderReusableView.swift} | 18 +-- .../Calendar/CalendarReusableView.swift | 32 ----- 6 files changed, 148 insertions(+), 159 deletions(-) rename VDS/Components/Calendar/{CalendarDateCollectionViewCell.swift => CalendarDateViewCell.swift} (68%) rename VDS/Components/Calendar/{CalendarFooterView.swift => CalendarFooterReusableView.swift} (80%) rename VDS/Components/Calendar/{CalendarHeaderView.swift => CalendarHeaderReusableView.swift} (95%) delete mode 100644 VDS/Components/Calendar/CalendarReusableView.swift diff --git a/VDS.xcodeproj/project.pbxproj b/VDS.xcodeproj/project.pbxproj index d6c8c32c..f8b92433 100644 --- a/VDS.xcodeproj/project.pbxproj +++ b/VDS.xcodeproj/project.pbxproj @@ -10,7 +10,9 @@ 1808BEBC2BA41C3200129230 /* CarouselScrollbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1808BEBB2BA41C3200129230 /* CarouselScrollbar.swift */; }; 1808BEC02BA456B700129230 /* CarouselScrollbarChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 1808BEBF2BA456B700129230 /* CarouselScrollbarChangeLog.txt */; }; 1832AC572BA0791D008AE476 /* BreadcrumbCellItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1832AC562BA0791D008AE476 /* BreadcrumbCellItem.swift */; }; - 18408EDE2BE32C9900E8646B /* CalendarFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18408EDD2BE32C9900E8646B /* CalendarFooterView.swift */; }; + 1842B1DF2BECE28B0021AFCA /* CalendarDateViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1842B1DE2BECE28B0021AFCA /* CalendarDateViewCell.swift */; }; + 1842B1E12BECE7B70021AFCA /* CalendarHeaderReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1842B1E02BECE7B70021AFCA /* CalendarHeaderReusableView.swift */; }; + 1842B1E32BECF0A20021AFCA /* CalendarFooterReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1842B1E22BECF0A10021AFCA /* CalendarFooterReusableView.swift */; }; 18450CF12BA1B19C009FDF2A /* BreadcrumbsChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 18450CF02BA1B19C009FDF2A /* BreadcrumbsChangeLog.txt */; }; 1855EC662BAABF2A002ACAC2 /* BreadcrumbItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1855EC652BAABF2A002ACAC2 /* BreadcrumbItemModel.swift */; }; 186B2A8A2B88DA7F001AB71F /* TextAreaChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 186B2A892B88DA7F001AB71F /* TextAreaChangeLog.txt */; }; @@ -18,15 +20,12 @@ 186D13CF2BBC36EF00986B53 /* DropdownSelectChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 186D13CE2BBC36EE00986B53 /* DropdownSelectChangeLog.txt */; }; 18792A902B7431F2008C0D29 /* ButtonIconBadgeIndicatorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18792A8F2B7431F2008C0D29 /* ButtonIconBadgeIndicatorModel.swift */; }; 18A3F12A2BD9298900498E4A /* Calendar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18A3F1292BD9298900498E4A /* Calendar.swift */; }; - 18A3F1322BD944E800498E4A /* CalendarDateCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18A3F1312BD944E800498E4A /* CalendarDateCollectionViewCell.swift */; }; 18A65A022B96E848006602CC /* Breadcrumbs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18A65A012B96E848006602CC /* Breadcrumbs.swift */; }; 18A65A042B96F050006602CC /* BreadcrumbItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18A65A032B96F050006602CC /* BreadcrumbItem.swift */; }; 18B463A42BBD3C46005C4528 /* DropdownOptionModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18B463A32BBD3C46005C4528 /* DropdownOptionModel.swift */; }; 18BDEE822B75316E00452358 /* ButtonIconChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 18BDEE812B75316E00452358 /* ButtonIconChangeLog.txt */; }; 18FEA1AD2BDD137500A56439 /* CalendarIndicatorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18FEA1AC2BDD137500A56439 /* CalendarIndicatorModel.swift */; }; - 18FEA1B32BE0BC8700A56439 /* CalendarReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18FEA1B22BE0BC8700A56439 /* CalendarReusableView.swift */; }; 18FEA1B52BE0E63600A56439 /* Date+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18FEA1B42BE0E63600A56439 /* Date+Extension.swift */; }; - 18FEA1B72BE0EBFE00A56439 /* CalendarHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18FEA1B62BE0EBFE00A56439 /* CalendarHeaderView.swift */; }; 18FEA1B92BE1301700A56439 /* CalendarChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 18FEA1B82BE1301700A56439 /* CalendarChangeLog.txt */; }; 445BA07829C07B3D0036A7C5 /* Notification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 445BA07729C07B3D0036A7C5 /* Notification.swift */; }; 44604AD429CE186A00E62B51 /* NotificationButtonModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44604AD329CE186A00E62B51 /* NotificationButtonModel.swift */; }; @@ -212,7 +211,9 @@ 1808BEBB2BA41C3200129230 /* CarouselScrollbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarouselScrollbar.swift; sourceTree = ""; }; 1808BEBF2BA456B700129230 /* CarouselScrollbarChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = CarouselScrollbarChangeLog.txt; sourceTree = ""; }; 1832AC562BA0791D008AE476 /* BreadcrumbCellItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BreadcrumbCellItem.swift; sourceTree = ""; }; - 18408EDD2BE32C9900E8646B /* CalendarFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarFooterView.swift; sourceTree = ""; }; + 1842B1DE2BECE28B0021AFCA /* CalendarDateViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarDateViewCell.swift; sourceTree = ""; }; + 1842B1E02BECE7B70021AFCA /* CalendarHeaderReusableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarHeaderReusableView.swift; sourceTree = ""; }; + 1842B1E22BECF0A10021AFCA /* CalendarFooterReusableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarFooterReusableView.swift; sourceTree = ""; }; 18450CF02BA1B19C009FDF2A /* BreadcrumbsChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = BreadcrumbsChangeLog.txt; sourceTree = ""; }; 1855EC652BAABF2A002ACAC2 /* BreadcrumbItemModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BreadcrumbItemModel.swift; sourceTree = ""; }; 186B2A892B88DA7F001AB71F /* TextAreaChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = TextAreaChangeLog.txt; sourceTree = ""; }; @@ -220,15 +221,12 @@ 186D13CE2BBC36EE00986B53 /* DropdownSelectChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = DropdownSelectChangeLog.txt; sourceTree = ""; }; 18792A8F2B7431F2008C0D29 /* ButtonIconBadgeIndicatorModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonIconBadgeIndicatorModel.swift; sourceTree = ""; }; 18A3F1292BD9298900498E4A /* Calendar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Calendar.swift; sourceTree = ""; }; - 18A3F1312BD944E800498E4A /* CalendarDateCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarDateCollectionViewCell.swift; sourceTree = ""; }; 18A65A012B96E848006602CC /* Breadcrumbs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Breadcrumbs.swift; sourceTree = ""; }; 18A65A032B96F050006602CC /* BreadcrumbItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BreadcrumbItem.swift; sourceTree = ""; }; 18B463A32BBD3C46005C4528 /* DropdownOptionModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DropdownOptionModel.swift; sourceTree = ""; }; 18BDEE812B75316E00452358 /* ButtonIconChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = ButtonIconChangeLog.txt; sourceTree = ""; }; 18FEA1AC2BDD137500A56439 /* CalendarIndicatorModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarIndicatorModel.swift; sourceTree = ""; }; - 18FEA1B22BE0BC8700A56439 /* CalendarReusableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarReusableView.swift; sourceTree = ""; }; 18FEA1B42BE0E63600A56439 /* Date+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+Extension.swift"; sourceTree = ""; }; - 18FEA1B62BE0EBFE00A56439 /* CalendarHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarHeaderView.swift; sourceTree = ""; }; 18FEA1B82BE1301700A56439 /* CalendarChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = CalendarChangeLog.txt; sourceTree = ""; }; 445BA07729C07B3D0036A7C5 /* Notification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notification.swift; sourceTree = ""; }; 44604AD329CE186A00E62B51 /* NotificationButtonModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationButtonModel.swift; sourceTree = ""; }; @@ -445,11 +443,10 @@ isa = PBXGroup; children = ( 18A3F1292BD9298900498E4A /* Calendar.swift */, - 18A3F1312BD944E800498E4A /* CalendarDateCollectionViewCell.swift */, - 18408EDD2BE32C9900E8646B /* CalendarFooterView.swift */, - 18FEA1B62BE0EBFE00A56439 /* CalendarHeaderView.swift */, + 1842B1DE2BECE28B0021AFCA /* CalendarDateViewCell.swift */, + 1842B1E22BECF0A10021AFCA /* CalendarFooterReusableView.swift */, + 1842B1E02BECE7B70021AFCA /* CalendarHeaderReusableView.swift */, 18FEA1AC2BDD137500A56439 /* CalendarIndicatorModel.swift */, - 18FEA1B22BE0BC8700A56439 /* CalendarReusableView.swift */, 18FEA1B42BE0E63600A56439 /* Date+Extension.swift */, 18FEA1B82BE1301700A56439 /* CalendarChangeLog.txt */, ); @@ -1149,6 +1146,7 @@ EA985C2D296F03FE00F2FF2E /* TileletIconModels.swift in Sources */, EA89200428AECF4B006B9984 /* UITextField+Publisher.swift in Sources */, 18A65A022B96E848006602CC /* Breadcrumbs.swift in Sources */, + 1842B1E12BECE7B70021AFCA /* CalendarHeaderReusableView.swift in Sources */, EA0D1C3F2A6AD5E200E5C127 /* Typography+ContentSizeCategory.swift in Sources */, EA5F86C82A1BD99100BC83E4 /* TabModel.swift in Sources */, EA297A5729FB0A360031ED56 /* AppleGuidelinesTouchable.swift in Sources */, @@ -1190,7 +1188,6 @@ 71ACE89C2BA0451200FB6ADC /* PaginationContainer.swift in Sources */, EAC71A1F2A2E173D00E47A9F /* RadioButton.swift in Sources */, EA33622C2891E73B0071C351 /* FontProtocol.swift in Sources */, - 18A3F1322BD944E800498E4A /* CalendarDateCollectionViewCell.swift in Sources */, EA596ABD2A16B4EC00300C4B /* Tab.swift in Sources */, 71ACE89E2BA1CC1700FB6ADC /* TiletEyebrowModel.swift in Sources */, EAF7F11728A1475A00B287F5 /* RadioButtonItem.swift in Sources */, @@ -1206,7 +1203,6 @@ EA297A5529FB07760031ED56 /* TooltipLabelAttribute.swift in Sources */, EA985BEA29689B6D00F2FF2E /* TileletSubTitleModel.swift in Sources */, EA2DC9B02BE175BA004F58C5 /* RequiredRule.swift in Sources */, - 18FEA1B72BE0EBFE00A56439 /* CalendarHeaderView.swift in Sources */, EA3361C9289054C50071C351 /* Surfaceable.swift in Sources */, EAB5FEED2927E1B200998C17 /* ButtonGroupPositionLayout.swift in Sources */, EA4DB30228DCBCA500103EE3 /* Badge.swift in Sources */, @@ -1243,7 +1239,6 @@ EA985C7D297DAED300F2FF2E /* Primitive.swift in Sources */, EAF1FE9929D4850E00101452 /* Clickable.swift in Sources */, EAD0688E2A55F819002E3A2D /* Loader.swift in Sources */, - 18408EDE2BE32C9900E8646B /* CalendarFooterView.swift in Sources */, EAB5FEF829393A7200998C17 /* ButtonGroupConstants.swift in Sources */, EAA7456C2AB23E2000C1841F /* TooltipModel.swift in Sources */, EA3361AF288B26310071C351 /* FormFieldable.swift in Sources */, @@ -1258,7 +1253,6 @@ EA985C1D296CD13600F2FF2E /* BundleManager.swift in Sources */, EA0B18052A9E2D2D00F2D0CD /* SelectorBase.swift in Sources */, EAC71A1D2A2E155A00E47A9F /* Checkbox.swift in Sources */, - 18FEA1B32BE0BC8700A56439 /* CalendarReusableView.swift in Sources */, EAF7F0AB289B13FD00B287F5 /* TextStyleLabelAttribute.swift in Sources */, EAB1D29C28A5618900DAE764 /* RadioButtonGroup.swift in Sources */, EA81410B2A0E8E3C004F60D2 /* ButtonIcon.swift in Sources */, @@ -1272,10 +1266,12 @@ EA0B18062A9E2D2D00F2D0CD /* SelectorItemBase.swift in Sources */, EAB5FF0129424ACB00998C17 /* UIControl.swift in Sources */, EA985BF5296C60C000F2FF2E /* Icon.swift in Sources */, + 1842B1E32BECF0A20021AFCA /* CalendarFooterReusableView.swift in Sources */, EA3361AA288B25E40071C351 /* Disabling.swift in Sources */, EA3361B6288B2A410071C351 /* Control.swift in Sources */, 5F21D7BF28DCEB3D003E7CD6 /* Useable.swift in Sources */, EAF7F0B7289C12A600B287F5 /* UITapGestureRecognizer.swift in Sources */, + 1842B1DF2BECE28B0021AFCA /* CalendarDateViewCell.swift in Sources */, EA0D1C392A6AD4DF00E5C127 /* Typography+SpacingConfig.swift in Sources */, 18FEA1B52BE0E63600A56439 /* Date+Extension.swift in Sources */, EAB2376629E9952D00AABE9A /* UIApplication.swift in Sources */, diff --git a/VDS/Components/Calendar/Calendar.swift b/VDS/Components/Calendar/Calendar.swift index e2f101d4..fa843db0 100644 --- a/VDS/Components/Calendar/Calendar.swift +++ b/VDS/Components/Calendar/Calendar.swift @@ -95,7 +95,7 @@ open class CalendarBase: View { collectionView.showsHorizontalScrollIndicator = false collectionView.showsVerticalScrollIndicator = false collectionView.backgroundColor = .clear - collectionView.register(CalendarDateCollectionViewCell.self, forCellWithReuseIdentifier: CalendarDateCollectionViewCell.identifier) + collectionView.register(CalendarDateViewCell.self, forCellWithReuseIdentifier: CalendarDateViewCell.identifier) collectionView.register(CalendarHeaderReusableView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: CalendarHeaderReusableView.identifier) @@ -175,6 +175,12 @@ open class CalendarBase: View { /// Resets to default settings. open override func reset() { super.reset() + hideContainerBorder = false + hideCurrentDateIndicator = false + transparentBackground = false + activeDates = [] + inactiveDates = [] + indicators = [] } //-------------------------------------------------- @@ -216,7 +222,7 @@ extension CalendarBase: UICollectionViewDelegate, UICollectionViewDataSource, UI } public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { - guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CalendarDateCollectionViewCell.identifier, for: indexPath) as? CalendarDateCollectionViewCell else { return UICollectionViewCell() } + guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CalendarDateViewCell.identifier, for: indexPath) as? CalendarDateViewCell else { return UICollectionViewCell() } var indicatorCount = 0 if self.indicators.count > 0 { for x in (0...(self.indicators.count-1)) { @@ -273,7 +279,7 @@ extension CalendarBase: UICollectionViewDelegate, UICollectionViewDataSource, UI guard let footer = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: CalendarFooterReusableView.identifier, for: indexPath) as? CalendarFooterReusableView else { return UICollectionReusableView() } - footer.configure(with: surface, indicators: indicators) + footer.update(with: surface, indicators: indicators) return footer } } diff --git a/VDS/Components/Calendar/CalendarDateCollectionViewCell.swift b/VDS/Components/Calendar/CalendarDateViewCell.swift similarity index 68% rename from VDS/Components/Calendar/CalendarDateCollectionViewCell.swift rename to VDS/Components/Calendar/CalendarDateViewCell.swift index 40ba8b82..7bbe3fd0 100644 --- a/VDS/Components/Calendar/CalendarDateCollectionViewCell.swift +++ b/VDS/Components/Calendar/CalendarDateViewCell.swift @@ -1,19 +1,17 @@ // -// CalendarDateCollectionViewCell.swift +// CalendarDateViewCell.swift // VDS // // Created by Kanamarlapudi, Vasavi on 24/04/24. // -import Foundation import UIKit import VDSTokens -/// Calendar collection view cell for Date view -final class CalendarDateCollectionViewCell: UICollectionViewCell { +final class CalendarDateViewCell: UICollectionViewCell { - ///Identifier for the Calendar Date Cell - static let identifier: String = String(describing: CalendarDateCollectionViewCell.self) + ///Identifier for the Calendar Date Cell. + static let identifier: String = String(describing: CalendarDateViewCell.self) //-------------------------------------------------- // MARK: - Private Properties @@ -71,8 +69,7 @@ final class CalendarDateCollectionViewCell: UICollectionViewCell { //-------------------------------------------------- // MARK: - Private Methods //-------------------------------------------------- - - /// Configuring the cell with default setup + /// Configuring the cell with default setup. private func setUp() { isAccessibilityElement = false contentView.addSubview(containerView) @@ -96,18 +93,18 @@ final class CalendarDateCollectionViewCell: UICollectionViewCell { stackView.centerXAnchor.constraint(equalTo: centerXAnchor).activate() } - /// Updating UI based on selected date, modified indicators data along with surface - /// Enable/disable cell based on min date, max date, active dates, inactive dates + /// Updating UI based on selected date, modified indicators data along with surface. + /// Enable/disable cell based on min date, max date, active dates, inactive dates. func update(with surface: Surface, indicators: [CalendarBase.CalendarIndicatorModel], text: String, indicatorCount: Int, selectedDate: Date, displayDate: Date, hideDate: Bool, minDate: Date, maxDate: Date, activeDates: [Date], inactiveDates: [Date]) { stackView.arrangedSubviews.forEach { $0.removeFromSuperview() } numberLabel.surface = surface numberLabel.text = text - // enable/disable cells based on min date, max date and active/inactive dates + // enable/disable cells based on min date, max date and active/inactive dates. self.updateLabel(with:surface, displayDate: displayDate, minDate: minDate, maxDate: maxDate, activeDates: activeDates, inactiveDates: inactiveDates) - // handling inactive dates + // handling inactive dates. if inactiveDates.count > 0 { for x in (0...(inactiveDates.count-1)) { if (inactiveDates[x].monthInt == displayDate.monthInt) && (inactiveDates[x].yearInt == displayDate.yearInt) { @@ -118,7 +115,7 @@ final class CalendarDateCollectionViewCell: UICollectionViewCell { } } - // update text color, bg color, corner radius + // update text color, bg color, corner radius. if (numberLabel.text == self.getDay(with: selectedDate)) && (selectedDate.monthInt == displayDate.monthInt) && (selectedDate.yearInt == displayDate.yearInt) && numberLabel.isEnabled { numberLabel.textColor = selectedTextColorConfiguration.getColor(surface) layer.backgroundColor = selectedBackgroundColor.getColor(surface).cgColor @@ -129,20 +126,20 @@ final class CalendarDateCollectionViewCell: UICollectionViewCell { layer.cornerRadius = 0 } - // add indicators + // add indicators. if indicatorCount > 0 { for x in (0...(indicators.count-1)) { - // irrespective of month and year, if it needs to show indicators on every month - comment below first if condition - if (indicators[x].date.monthInt == displayDate.monthInt) && (indicators[x].date.yearInt == displayDate.yearInt) { + // irrespective of month and year, if it needs to show indicators on every month - comment below first if condition, else uncomment it. + // if (indicators[x].date.monthInt == displayDate.monthInt) && (indicators[x].date.yearInt == displayDate.yearInt) { if (self.numberLabel.text == self.getDay(with: indicators[x].date)) { let color = (numberLabel.text == self.getDay(with: selectedDate)) ? selectedCellIndicatorColorConfiguration.getColor(surface) : unselectedCellIndicatorColorConfiguration.getColor(surface) addIndicator(with: color, surface: surface, clearFullCircle: (x == 1), drawSemiCircle: (x == 2)) } - } + // } } } - // update text style for current date + // update text style for current date. if (numberLabel.text == self.getDay(with: currentDate)) && (currentDate.monthInt == displayDate.monthInt) && (currentDate.yearInt == displayDate.yearInt) { numberLabel.textStyle = hideDate ? .bodySmall : .boldBodySmall } else { @@ -166,7 +163,7 @@ final class CalendarDateCollectionViewCell: UICollectionViewCell { } } - // handing active dates if exist, else enable numberLabel to display day + // handing active dates if exist, else enable numberLabel to display day. func handleActiveDates(with displayDate: Date, activeDates: [Date], inactiveDates: [Date]) { if activeDates.count > 0 && inactiveDates.count == 0 { showActiveDates(with: displayDate, activeDates: activeDates, inactiveDates: inactiveDates) @@ -175,7 +172,7 @@ final class CalendarDateCollectionViewCell: UICollectionViewCell { } } - // enable all days if no active dates, handing active dates if exist + // enable all days if no active dates, handing active dates if exist. func enableAllDaysAndCheckActiveDates(with surface:Surface, displayDate: Date, activeDates: [Date], inactiveDates: [Date]) { if activeDates.count > 0 && inactiveDates.count == 0 { disableLabel(with: surface) @@ -184,49 +181,83 @@ final class CalendarDateCollectionViewCell: UICollectionViewCell { numberLabel.isEnabled = true } } + + func minDateValidation(with surface:Surface, minDate: Date, displayDate: Date, activeDates: [Date], inactiveDates: [Date]) { + // validate days to enable/disable with min date only. + if let day:Int = Int(numberLabel.text), day < minDate.dayInt { + disableLabel(with: surface) + } else { + numberLabel.isEnabled = false + handleActiveDates(with: displayDate, activeDates: activeDates, inactiveDates: inactiveDates) + } + } + + func maxDateValidation(with surface:Surface, maxDate: Date, displayDate: Date, activeDates: [Date], inactiveDates: [Date]) { + // validate days to enable/disable with max date only. + if let day:Int = Int(numberLabel.text), day > maxDate.dayInt { + disableLabel(with: surface) + } else { + numberLabel.isEnabled = false + handleActiveDates(with: displayDate, activeDates: activeDates, inactiveDates: inactiveDates) + } + } + + func minAndMaxDateValidation(with surface:Surface, minDate: Date, maxDate: Date, displayDate: Date, activeDates: [Date], inactiveDates: [Date]) { + // validate days to enable/disable with min and max date. + if let day:Int = Int(numberLabel.text), day < minDate.dayInt || day > maxDate.dayInt { + disableLabel(with: surface) + } else { + numberLabel.isEnabled = false + handleActiveDates(with: displayDate, activeDates: activeDates, inactiveDates: inactiveDates) + } + } - // enable/disable cells based on min date, max date and active/inactive dates + // enable/disable cells based on min date, max date and active/inactive dates. func updateLabel(with surface: Surface, displayDate: Date, minDate: Date, maxDate: Date, activeDates: [Date], inactiveDates: [Date]) { - if (minDate.yearInt == displayDate.yearInt) || (maxDate.yearInt == displayDate.yearInt) { + + if (minDate.yearInt == displayDate.yearInt) && !(maxDate.yearInt == displayDate.yearInt) { + // min year and max year are different, and matched to min year. + if (minDate.monthInt == displayDate.monthInt) { + // min year and max year are different, and matched to min year and min month. + minDateValidation(with: surface, minDate: minDate, displayDate: displayDate, activeDates: activeDates, inactiveDates: inactiveDates) + } else { + // handing active dates - enable all days if no active dates. + enableAllDaysAndCheckActiveDates(with: surface, displayDate: displayDate, activeDates: activeDates, inactiveDates: inactiveDates) + } + } else if (maxDate.yearInt == displayDate.yearInt) && !((minDate.yearInt == displayDate.yearInt)) { + // min year and max year are different, and matched to max year. + if (maxDate.monthInt == displayDate.monthInt) { + // min year and max year are different, and matched to max year and max month. + maxDateValidation(with: surface, maxDate: maxDate, displayDate: displayDate, activeDates: activeDates, inactiveDates: inactiveDates) + } else { + // handing active dates - enable all days if no active dates. + enableAllDaysAndCheckActiveDates(with: surface, displayDate: displayDate, activeDates: activeDates, inactiveDates: inactiveDates) + } + } else if (minDate.yearInt == displayDate.yearInt) && (maxDate.yearInt == displayDate.yearInt) { + // min year and max year same if (minDate.monthInt == displayDate.monthInt) && (maxDate.monthInt == displayDate.monthInt) { - // validate days to enable/disable - if let day:Int = Int(numberLabel.text), day < minDate.dayInt || day > maxDate.dayInt { - disableLabel(with: surface) - } else { - numberLabel.isEnabled = false - handleActiveDates(with: displayDate, activeDates: activeDates, inactiveDates: inactiveDates) - } + // min year and max year same, when choose dates in same month. + minAndMaxDateValidation(with: surface, minDate: minDate, maxDate: maxDate, displayDate: displayDate, activeDates: activeDates, inactiveDates: inactiveDates) + } else if (minDate.monthInt == displayDate.monthInt) || (maxDate.monthInt == displayDate.monthInt) { + // min year and max year same, and choose dates in different months. if (minDate.monthInt == displayDate.monthInt) { - // validate days to enable/disable - if let day:Int = Int(numberLabel.text), day < minDate.dayInt { - disableLabel(with: surface) - } else { - numberLabel.isEnabled = false - handleActiveDates(with: displayDate, activeDates: activeDates, inactiveDates: inactiveDates) - } - + // min year and max year same, and matched to min month. + minDateValidation(with: surface, minDate: minDate, displayDate: displayDate, activeDates: activeDates, inactiveDates: inactiveDates) } else if (maxDate.monthInt == displayDate.monthInt) { - // validate days to enable/disable - if let day:Int = Int(numberLabel.text), day > maxDate.dayInt { - disableLabel(with: surface) - } else { - numberLabel.isEnabled = false - handleActiveDates(with: displayDate, activeDates: activeDates, inactiveDates: inactiveDates) - } - + // min year and max year same, and matched to max month. + maxDateValidation(with: surface, maxDate: maxDate, displayDate: displayDate, activeDates: activeDates, inactiveDates: inactiveDates) } - } else if ((minDate.monthInt < displayDate.monthInt) || (displayDate.monthInt < maxDate.monthInt)) { - // enable all days if no active dates - // handing active dates + } else { + // min year and max year same, and not matched to min or max month. + // handing active dates - enable all days if no active dates. enableAllDaysAndCheckActiveDates(with: surface, displayDate: displayDate, activeDates: activeDates, inactiveDates: inactiveDates) } } else { - // enable all days if no active dates - // handing active dates + // min year and max year are different, and not matched to min or max year. + // handing active dates - enable all days if no active dates. enableAllDaysAndCheckActiveDates(with: surface, displayDate: displayDate, activeDates: activeDates, inactiveDates: inactiveDates) } - } func addIndicator(with color: UIColor, surface: Surface, clearFullCircle: Bool, drawSemiCircle: Bool) { diff --git a/VDS/Components/Calendar/CalendarFooterView.swift b/VDS/Components/Calendar/CalendarFooterReusableView.swift similarity index 80% rename from VDS/Components/Calendar/CalendarFooterView.swift rename to VDS/Components/Calendar/CalendarFooterReusableView.swift index 1ebd73be..3e85a6ec 100644 --- a/VDS/Components/Calendar/CalendarFooterView.swift +++ b/VDS/Components/Calendar/CalendarFooterReusableView.swift @@ -1,40 +1,24 @@ // -// CalendarFooterView.swift +// CalendarFooterReusableView.swift // VDS // // Created by Kanamarlapudi, Vasavi on 29/04/24. // -import Foundation import UIKit import VDSTokens -import Combine /// Footer view to show indicators data. -open class CalendarFooterView: View { - //-------------------------------------------------- - // MARK: - Initializers - //-------------------------------------------------- - required public init() { - super.init(frame: .zero) - } +class CalendarFooterReusableView: UICollectionReusableView { - public override init(frame: CGRect) { - super.init(frame: .zero) - } - - public required init?(coder: NSCoder) { - super.init(coder: coder) - } - - //-------------------------------------------------- - // MARK: - Public Properties - //-------------------------------------------------- - open var items: [CalendarBase.CalendarIndicatorModel] = [] { didSet { setNeedsUpdate() } } - + ///Identifier for the Calendar Footer Reusable View. + static let identifier: String = String(describing: CalendarFooterReusableView.self) + //-------------------------------------------------- // MARK: - Private Properties //-------------------------------------------------- + private var surface: Surface = .light + private var items: [CalendarBase.CalendarIndicatorModel] = [] internal var containerSize: CGSize { CGSize(width: 304, height: 40) } internal var containerView = View().with { @@ -60,50 +44,51 @@ open class CalendarFooterView: View { } //-------------------------------------------------- - // MARK: - Lifecycle + // MARK: - Initializers //-------------------------------------------------- - open override func initialSetup() { - super.initialSetup() + override init(frame: CGRect) { + super.init(frame: frame) + setUp() } - open override func setup() { - super.setup() + required init?(coder: NSCoder) { + super.init(coder: coder) + setUp() + } + + //-------------------------------------------------- + // MARK: - Private Methods + //-------------------------------------------------- + /// Configuring the cell with default setup. + private func setUp() { isAccessibilityElement = false addSubview(containerView) containerView - .pinTop(VDSLayout.space6X) +// .pinTop(VDSLayout.space6X) + .pinTopLessThanOrEqualTo(topAnchor, VDSLayout.space6X, .defaultLow) .pinBottom() - .pinLeadingGreaterThanOrEqualTo(leadingAnchor, VDSLayout.space3X, .defaultLow) + .pinLeadingGreaterThanOrEqualTo(leadingAnchor, VDSLayout.space3X, .defaultHigh) .pinTrailingLessThanOrEqualTo(trailingAnchor, VDSLayout.space3X, .defaultHigh) - .height(containerSize.height) - .width(containerSize.width) - - containerView.centerXAnchor.constraint(equalTo: centerXAnchor).activate() - + .width(containerSize.width - (2*VDSLayout.space3X)) + .heightLessThanEqualTo(containerSize.height) + // legend Collection View containerView.addSubview(legendCollectionView) - legendCollectionView.pinToSuperView() + legendCollectionView.pinTop().pinBottom().pinLeading().pinTrailing().pinCenterY().pinCenterX() } - open override func updateView() { - super.updateView() + /// Updating UI to show legend with titles. + func update(with surface: Surface, indicators: [CalendarBase.CalendarIndicatorModel]) { + self.items = indicators + self.surface = surface legendCollectionView.reloadData() - setNeedsLayout() - layoutIfNeeded() } - override open func layoutSubviews() { - super.layoutSubviews() - } - - open override func reset() { - super.reset() - } } -extension CalendarFooterView: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { +extension CalendarFooterReusableView: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return items.count @@ -114,7 +99,7 @@ extension CalendarFooterView: UICollectionViewDelegate, UICollectionViewDataSour let cell = collectionView.dequeueReusableCell(withReuseIdentifier: LegendCollectionViewCell.identifier, for: indexPath) as? LegendCollectionViewCell, indexPath.row <= items.count else { return UICollectionViewCell() } let text = items[indexPath.row].label - cell.updateTitle(text: text, color: VDSColor.elementsSecondaryOnlight, surface: surface, clearFullcircle: indexPath.row == 1, drawSemiCircle: indexPath.row == 2) + cell.updateTitle(text: text, color: VDSColor.elementsSecondaryOnlight, surface: self.surface, clearFullcircle: indexPath.row == 1, drawSemiCircle: indexPath.row == 2) return cell } } @@ -133,6 +118,7 @@ private class LegendCollectionViewCell: UICollectionViewCell { $0.numberOfLines = 1 $0.textStyle = .bodySmall $0.isAccessibilityElement = false + $0.backgroundColor = .clear } private var legendIndicatorWrapper: View = View().with { diff --git a/VDS/Components/Calendar/CalendarHeaderView.swift b/VDS/Components/Calendar/CalendarHeaderReusableView.swift similarity index 95% rename from VDS/Components/Calendar/CalendarHeaderView.swift rename to VDS/Components/Calendar/CalendarHeaderReusableView.swift index cdb08a18..b1d18810 100644 --- a/VDS/Components/Calendar/CalendarHeaderView.swift +++ b/VDS/Components/Calendar/CalendarHeaderReusableView.swift @@ -1,27 +1,26 @@ // -// CalendarHeaderView.swift +// CalendarHeaderReusableView.swift // VDS // // Created by Kanamarlapudi, Vasavi on 30/04/24. // -import Foundation import UIKit import VDSTokens -/// Header view to display month and year along with days of week +/// Header view to display month and year along with days of week. class CalendarHeaderReusableView: UICollectionReusableView { - ///Identifier for the Calendar Header Reusable View + ///Identifier for the Calendar Header Reusable View. static let identifier: String = String(describing: CalendarHeaderReusableView.self) //-------------------------------------------------- // MARK: - Public Properties //-------------------------------------------------- - /// A callback when the next button clicked + /// A callback when the next button clicked. public var nextClicked: (() -> (Void))? - /// A callback when the previous button clicked + /// A callback when the previous button clicked. public var previousClicked: (() -> (Void))? //-------------------------------------------------- @@ -106,7 +105,10 @@ class CalendarHeaderReusableView: UICollectionReusableView { setUp() } - /// Configuring the cell with default setup + //-------------------------------------------------- + // MARK: - Private Methods + //-------------------------------------------------- + /// Configuring the cell with default setup. private func setUp() { isAccessibilityElement = false @@ -150,7 +152,7 @@ class CalendarHeaderReusableView: UICollectionReusableView { } /// Updating UI based on next/previous clicks along with surface. - /// Updating UI to enable/disable the next & previous buttons, updating header title + /// Updating UI to enable/disable the next & previous buttons, updating header title. func update(with surface: Surface, aDate: Date, nextEnabled: Bool, previousEnabled: Bool) { self.surface = surface headerTitle.surface = surface diff --git a/VDS/Components/Calendar/CalendarReusableView.swift b/VDS/Components/Calendar/CalendarReusableView.swift deleted file mode 100644 index 4a695ab0..00000000 --- a/VDS/Components/Calendar/CalendarReusableView.swift +++ /dev/null @@ -1,32 +0,0 @@ -// -// CalendarReusableView.swift -// VDS -// -// Created by Kanamarlapudi, Vasavi on 24/04/24. -// - -import UIKit -import VDSTokens - -/// Custom footer view -class CalendarFooterReusableView: UICollectionReusableView { - - ///Identifier for the Calendar Footer Reusable View - static let identifier: String = String(describing: CalendarFooterReusableView.self) - - private lazy var footerView = CalendarFooterView() - - override init(frame: CGRect) { - super.init(frame: frame) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - func configure(with surface: Surface, indicators: [CalendarBase.CalendarIndicatorModel]) { - footerView.items = indicators - footerView.surface = surface - addSubview(footerView) - } -} From 20f5926bbf6ad810f605adb4e54998a75a111bdf Mon Sep 17 00:00:00 2001 From: vasavk Date: Fri, 10 May 2024 13:18:55 +0530 Subject: [PATCH 22/46] Digital ACT-191 ONEAPP-7958 story: callback when the date changes and is in enabled state only. --- VDS/Components/Calendar/Calendar.swift | 27 ++++++++++++------- .../Calendar/CalendarDateViewCell.swift | 5 ++++ 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/VDS/Components/Calendar/Calendar.swift b/VDS/Components/Calendar/Calendar.swift index fa843db0..bcba3885 100644 --- a/VDS/Components/Calendar/Calendar.swift +++ b/VDS/Components/Calendar/Calendar.swift @@ -287,17 +287,24 @@ extension CalendarBase: UICollectionViewDelegate, UICollectionViewDataSource, UI } public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { - let selectedItem = Calendar.current.date(byAdding: .day, value: 1, to: self.dates[indexPath.row])! - onChangeSelectedDate?(selectedItem) - - selectedDate = self.dates[indexPath.row] - displayDate = selectedDate - var reloadIndexPaths = [indexPath] + // reload selected index, if it is in enabled state. + if let cell = collectionView.cellForItem(at: indexPath) as? CalendarDateViewCell { + let isEnabled: Bool = cell.isDateEnabled() + if isEnabled { + // Callback to pass selected date if it is enabled only. + let selectedItem = Calendar.current.date(byAdding: .day, value: 1, to: self.dates[indexPath.row])! + onChangeSelectedDate?(selectedItem) + + selectedDate = self.dates[indexPath.row] + displayDate = selectedDate + var reloadIndexPaths = [indexPath] - // If an cell is already selected, then it needs to be deselected. - // Add its index path to the array of index paths to be reloaded. - if let deselectIndexPath = selectedIndexPath { reloadIndexPaths.append(deselectIndexPath) } - self.collectionView.reloadItems(at: reloadIndexPaths) + // If an cell is already selected, then it needs to be deselected. + // Add its index path to the array of index paths to be reloaded. + if let deselectIndexPath = selectedIndexPath { reloadIndexPaths.append(deselectIndexPath) } + self.collectionView.reloadItems(at: reloadIndexPaths) + } + } } public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize { diff --git a/VDS/Components/Calendar/CalendarDateViewCell.swift b/VDS/Components/Calendar/CalendarDateViewCell.swift index 7bbe3fd0..26886d0a 100644 --- a/VDS/Components/Calendar/CalendarDateViewCell.swift +++ b/VDS/Components/Calendar/CalendarDateViewCell.swift @@ -147,6 +147,11 @@ final class CalendarDateViewCell: UICollectionViewCell { } } + // returns cell enabled state. + func isDateEnabled() -> Bool { + return numberLabel.isEnabled + } + func disableLabel(with surface: Surface) { numberLabel.isEnabled = false numberLabel.textColor = disabledTextColorConfiguration.getColor(surface) From 843c8054ad10bffc630a62cbc2cf9bbf1aa42f88 Mon Sep 17 00:00:00 2001 From: vasavk Date: Fri, 10 May 2024 22:40:37 +0530 Subject: [PATCH 23/46] Digital ACT-191 ONEAPP-7958 story: dynamic height update for calendar --- VDS/Components/Calendar/Calendar.swift | 28 +++++++++++++------- VDS/Components/Calendar/Date+Extension.swift | 4 ++- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/VDS/Components/Calendar/Calendar.swift b/VDS/Components/Calendar/Calendar.swift index bcba3885..9ec8e6fc 100644 --- a/VDS/Components/Calendar/Calendar.swift +++ b/VDS/Components/Calendar/Calendar.swift @@ -74,8 +74,9 @@ open class CalendarBase: View { private let headerHeight = 88.0 private let footerHeight = 40.0 private let calendarWidth = 304.0 - private let items = 35 - + + private var heightConstraint: NSLayoutConstraint? + private var containerHeightConstraint: NSLayoutConstraint? private var selectedIndexPath : IndexPath? private var dates: [Date] = [] private var days: [String] = [] @@ -121,15 +122,16 @@ open class CalendarBase: View { /// Called once when a view is initialized and is used to Setup additional UI or other constants and configurations. open override func setup() { super.setup() - isAccessibilityElement = false + isAccessibilityElement = true + accessibilityLabel = "Calendar" addSubview(containerView) containerView .pinTop() .pinBottom() .pinLeadingGreaterThanOrEqualTo() .pinTrailingLessThanOrEqualTo() - .height(containerSize.height) .width(containerSize.width) + .heightGreaterThanEqualTo(containerSize.height) containerView.centerXAnchor.constraint(equalTo: centerXAnchor).activate() // Calendar View @@ -142,10 +144,10 @@ open class CalendarBase: View { .pinLeading(spacing) .pinTrailing(spacing) .width(calendarWidth) - .height(calendarHeight) + .heightGreaterThanEqualTo(calendarHeight) collectionView.centerXAnchor.constraint(equalTo: containerView.centerXAnchor).activate() } - + open override func updateView() { super.updateView() // range check between min & max dates @@ -154,8 +156,8 @@ open class CalendarBase: View { let fallsBetween = displayDate.isBetweeen(date: minDate, andDate: maxDate) displayDate = fallsBetween ? displayDate : minDate self.fetchDates(with: displayDate) - collectionView.reloadData() } + layer.backgroundColor = backgroundColorConfiguration.getColor(self).cgColor if hideContainerBorder { layer.borderColor = nil @@ -187,6 +189,8 @@ open class CalendarBase: View { // MARK: - Private Methods //-------------------------------------------------- func fetchDates(with aDate:Date) { + heightConstraint?.isActive = false + containerHeightConstraint?.isActive = false days.removeAll() self.dates = aDate.calendarDisplayDays for date in dates { @@ -197,6 +201,14 @@ open class CalendarBase: View { days.append(getDay(with: date)) } } + self.collectionView.reloadData() + var height = self.collectionView.collectionViewLayout.collectionViewContentSize.height + height = (height > 0) ? height : containerSize.height + heightConstraint = collectionView.heightAnchor.constraint(equalToConstant: height) + containerHeightConstraint = containerView.heightAnchor.constraint(equalToConstant: (height + (2 * VDSLayout.space4X))) + heightConstraint?.isActive = true + containerHeightConstraint?.isActive = true + self.layoutIfNeeded() } func getDay(with date:Date) -> String { @@ -259,7 +271,6 @@ extension CalendarBase: UICollectionViewDelegate, UICollectionViewDataSource, UI if ((aDate.monthInt <= maxDate.monthInt) && (aDate.yearInt == maxDate.yearInt)) || (aDate.yearInt < maxDate.yearInt) { displayDate = aDate self.fetchDates(with: displayDate) - self.collectionView.reloadData() } } header.previousClicked = { [weak self] in @@ -268,7 +279,6 @@ extension CalendarBase: UICollectionViewDelegate, UICollectionViewDataSource, UI if ((minDate.monthInt <= aDate.monthInt) && (minDate.yearInt == aDate.yearInt)) || (minDate.yearInt < aDate.yearInt) { displayDate = aDate self.fetchDates(with: displayDate) - self.collectionView.reloadData() } } header.update(with: surface, aDate: displayDate, nextEnabled: nextEnabled, previousEnabled: prevEnabled) diff --git a/VDS/Components/Calendar/Date+Extension.swift b/VDS/Components/Calendar/Date+Extension.swift index 2a016ecb..8d697049 100644 --- a/VDS/Components/Calendar/Date+Extension.swift +++ b/VDS/Components/Calendar/Date+Extension.swift @@ -92,7 +92,9 @@ extension Date { /// Check if the date falls between the given dates func isBetweeen(date date1: Date, andDate date2: Date) -> Bool { - return date1.compare(self) == self.compare(date2) + let from = Calendar.current.date(byAdding: .day, value: -1, to: date1)! + let to = Calendar.current.date(byAdding: .day, value: 1, to: date2)! + return from.compare(self) == self.compare(to) } /// Returns the month name of the given date From 8f529251fd79e0315d716f7469b990fc8055ee78 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Mon, 13 May 2024 10:11:15 -0500 Subject: [PATCH 24/46] added datePicker Signed-off-by: Matt Bruce --- VDS.xcodeproj/project.pbxproj | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/VDS.xcodeproj/project.pbxproj b/VDS.xcodeproj/project.pbxproj index c60f125a..d04570b2 100644 --- a/VDS.xcodeproj/project.pbxproj +++ b/VDS.xcodeproj/project.pbxproj @@ -150,6 +150,7 @@ EAC58C142BED0DEC00BA39FA /* Number.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC58C132BED0DEC00BA39FA /* Number.swift */; }; EAC58C162BED0E0300BA39FA /* InlineAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC58C152BED0E0300BA39FA /* InlineAction.swift */; }; EAC58C182BED0E2300BA39FA /* SecurityCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC58C172BED0E2300BA39FA /* SecurityCode.swift */; }; + EAC58C212BF127FE00BA39FA /* DatePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC58C202BF127FE00BA39FA /* DatePicker.swift */; }; EAC71A1D2A2E155A00E47A9F /* Checkbox.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC71A1C2A2E155A00E47A9F /* Checkbox.swift */; }; EAC71A1F2A2E173D00E47A9F /* RadioButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC71A1E2A2E173D00E47A9F /* RadioButton.swift */; }; EAC846F3294B95CE00F685BA /* ButtonGroupCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC846F2294B95CE00F685BA /* ButtonGroupCollectionViewCell.swift */; }; @@ -355,6 +356,7 @@ EAC58C132BED0DEC00BA39FA /* Number.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Number.swift; sourceTree = ""; }; EAC58C152BED0E0300BA39FA /* InlineAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InlineAction.swift; sourceTree = ""; }; EAC58C172BED0E2300BA39FA /* SecurityCode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecurityCode.swift; sourceTree = ""; }; + EAC58C202BF127FE00BA39FA /* DatePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = DatePicker.swift; path = ../../../../../../../../../Downloads/DatePicker.swift; sourceTree = ""; }; EAC71A1C2A2E155A00E47A9F /* Checkbox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Checkbox.swift; sourceTree = ""; }; EAC71A1E2A2E173D00E47A9F /* RadioButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioButton.swift; sourceTree = ""; }; EAC846F2294B95CE00F685BA /* ButtonGroupCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonGroupCollectionViewCell.swift; sourceTree = ""; }; @@ -612,6 +614,7 @@ EA0FC2BE2912D18200DF80B4 /* Buttons */, 1808BEBA2BA41B1D00129230 /* CarouselScrollbar */, EAF7F092289985E200B287F5 /* Checkbox */, + EAC58C1F2BF127F000BA39FA /* DatePicker */, 186D13C92BBA8A3500986B53 /* DropdownSelect */, EA985BF3296C609E00F2FF2E /* Icon */, EA3362412892EF700071C351 /* Label */, @@ -909,6 +912,14 @@ path = FieldTypes; sourceTree = ""; }; + EAC58C1F2BF127F000BA39FA /* DatePicker */ = { + isa = PBXGroup; + children = ( + EAC58C202BF127FE00BA39FA /* DatePicker.swift */, + ); + path = DatePicker; + sourceTree = ""; + }; EAC9257E29119B5D00091998 /* TextLink */ = { isa = PBXGroup; children = ( @@ -1157,6 +1168,7 @@ 1832AC572BA0791D008AE476 /* BreadcrumbCellItem.swift in Sources */, EA3361C328902D960071C351 /* Toggle.swift in Sources */, EAF7F0A0289AB7EC00B287F5 /* View.swift in Sources */, + EAC58C212BF127FE00BA39FA /* DatePicker.swift in Sources */, EA89201328B568D8006B9984 /* RadioBoxItem.swift in Sources */, 71FC86E42B9841AC00700965 /* PaginationFlowLayout.swift in Sources */, EAC9258C2911C9DE00091998 /* InputField.swift in Sources */, From e7e6a68458e04a8e83d7b148fa364152e8036b11 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Mon, 13 May 2024 10:11:25 -0500 Subject: [PATCH 25/46] added calendar Signed-off-by: Matt Bruce --- VDS/Components/Icon/IconName.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/VDS/Components/Icon/IconName.swift b/VDS/Components/Icon/IconName.swift index 3a17f686..27f57787 100644 --- a/VDS/Components/Icon/IconName.swift +++ b/VDS/Components/Icon/IconName.swift @@ -48,6 +48,7 @@ extension Icon { internal static let paginationRightCaret = Name(name: "pagination-right-caret") internal static let verizonUp = Name(name: "verizon-up") internal static let warningBold = Name(name: "warning-bold") + public static let calendar = Name(name: "calendar") public static let checkmark = Name(name: "checkmark") public static let checkmarkAlt = Name(name: "checkmark-alt") public static let close = Name(name: "close") From cd0b06670145bd273e77739bcc42fad069c8e037 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Mon, 13 May 2024 11:19:57 -0500 Subject: [PATCH 26/46] refactored entryfield base Signed-off-by: Matt Bruce --- .../DropdownSelect/DropdownSelect.swift | 41 +++++++++++------ .../TextFields/EntryFieldBase.swift | 46 ++++++++----------- .../InputField/FieldTypes/FieldType.swift | 8 ---- .../TextFields/InputField/InputField.swift | 37 ++++++++++++--- 4 files changed, 78 insertions(+), 54 deletions(-) diff --git a/VDS/Components/DropdownSelect/DropdownSelect.swift b/VDS/Components/DropdownSelect/DropdownSelect.swift index bf1603a9..411f8327 100644 --- a/VDS/Components/DropdownSelect/DropdownSelect.swift +++ b/VDS/Components/DropdownSelect/DropdownSelect.swift @@ -72,6 +72,18 @@ open class DropdownSelect: EntryFieldBase { internal var minWidthDefault = 66.0 internal var minWidthInlineLabel = 102.0 internal var minWidth: CGFloat { showInlineLabel ? minWidthInlineLabel : minWidthDefault } + + /// The is used for the for adding the helperLabel to the right of the containerView. + internal var horizontalStackView: UIStackView = { + return UIStackView().with { + $0.translatesAutoresizingMaskIntoConstraints = false + $0.axis = .horizontal + $0.distribution = .fillEqually + $0.spacing = VDSLayout.space3X + $0.alignment = .top + } + }() + //-------------------------------------------------- // MARK: - Public Properties //-------------------------------------------------- @@ -190,14 +202,6 @@ open class DropdownSelect: EntryFieldBase { dropdownField.isUserInteractionEnabled = isReadOnly ? false : true selectedOptionLabel.surface = surface selectedOptionLabel.isEnabled = isEnabled - - //set the width constraints - let maxwidth = frame.size.width - if let width, width > minWidth && width < maxwidth { - widthConstraint?.constant = width - } else { - widthConstraint?.constant = maxwidth >= minWidth ? maxwidth : minWidth - } } /// Resets to default settings. @@ -280,6 +284,7 @@ open class DropdownSelect: EntryFieldBase { open override func updateHelperLabel(){ //remove first + secondaryStackView.removeFromSuperview() helperLabel.removeFromSuperview() super.updateHelperLabel() @@ -287,14 +292,24 @@ open class DropdownSelect: EntryFieldBase { //set the helper label position if helperText != nil { if helperTextPlacement == .right { - middleStackView.spacing = VDSLayout.space3X - middleStackView.distribution = .fillEqually - middleStackView.addArrangedSubview(helperLabel) + horizontalStackView.addArrangedSubview(secondaryStackView) + horizontalStackView.addArrangedSubview(helperLabel) + primaryStackView.addArrangedSubview(horizontalStackView) } else { - middleStackView.spacing = 0 - middleStackView.distribution = .fill bottomContainerStackView.addArrangedSubview(helperLabel) + primaryStackView.addArrangedSubview(secondaryStackView) } + } else { + primaryStackView.addArrangedSubview(secondaryStackView) + } + + //set the width constraints + let frameWidth = frame.size.width + let maxwidth = helperTextPlacement == .right ? (frameWidth - horizontalStackView.spacing) / 2 : frameWidth + if let width, width > minWidth && width < maxwidth { + widthConstraint?.constant = width + } else { + widthConstraint?.constant = maxwidth >= minWidth ? maxwidth : minWidth } } diff --git a/VDS/Components/TextFields/EntryFieldBase.swift b/VDS/Components/TextFields/EntryFieldBase.swift index f34a2259..6dca8a0e 100644 --- a/VDS/Components/TextFields/EntryFieldBase.swift +++ b/VDS/Components/TextFields/EntryFieldBase.swift @@ -40,7 +40,7 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { //-------------------------------------------------- // MARK: - Private Properties //-------------------------------------------------- - internal var stackView: UIStackView = { + internal var primaryStackView: UIStackView = { return UIStackView().with { $0.translatesAutoresizingMaskIntoConstraints = false $0.axis = .vertical @@ -48,22 +48,23 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { $0.alignment = .leading } }() - - internal var middleStackView: UIStackView = { - return UIStackView().with { - $0.translatesAutoresizingMaskIntoConstraints = false - $0.axis = .horizontal - $0.distribution = .fill - $0.alignment = .top - } - }() + internal let secondaryStackView = UIStackView().with { + $0.translatesAutoresizingMaskIntoConstraints = false + $0.axis = .vertical + $0.distribution = .fill + } + + /// This is the view that will be wrapped with the border for userInteraction. internal var containerView: UIView = { return UIView().with { $0.translatesAutoresizingMaskIntoConstraints = false } }() + /// This is a horizontal Stack View that is placed inside the containterView (bordered view) + /// The first arrangedView will be the view from getFieldContainer() + /// The second view is the statusIcon. internal var fieldStackView: UIStackView = { return UIStackView().with { $0.translatesAutoresizingMaskIntoConstraints = false @@ -73,6 +74,7 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { } }() + /// This is a vertical stack used for the errorLabel and helperLabel. internal var bottomContainerStackView: UIStackView = { return UIStackView().with { $0.translatesAutoresizingMaskIntoConstraints = false @@ -207,22 +209,14 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { super.setup() isAccessibilityElement = false - addSubview(stackView) + addSubview(primaryStackView) //create the wrapping view heightConstraint = containerView.heightGreaterThanEqualTo(constant: containerSize.height) widthConstraint = containerView.width(constant: 0) - let leftStackView = UIStackView().with { - $0.translatesAutoresizingMaskIntoConstraints = false - $0.axis = .vertical - $0.distribution = .fill - } - leftStackView.addArrangedSubview(containerView) - leftStackView.setCustomSpacing(8, after: containerView) - - //add the containerView to the middleStack - middleStackView.addArrangedSubview(leftStackView) + secondaryStackView.addArrangedSubview(containerView) + secondaryStackView.setCustomSpacing(8, after: containerView) //add ContainerStackView //this is the horizontal stack that contains @@ -246,13 +240,13 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { bottomContainerStackView.addArrangedSubview(errorLabel) bottomContainerStackView.addArrangedSubview(helperLabel) - stackView.addArrangedSubview(titleLabel) - stackView.addArrangedSubview(middleStackView) - leftStackView.addArrangedSubview(bottomContainer) + primaryStackView.addArrangedSubview(titleLabel) + primaryStackView.addArrangedSubview(secondaryStackView) + secondaryStackView.addArrangedSubview(bottomContainer) - stackView.setCustomSpacing(4, after: titleLabel) + primaryStackView.setCustomSpacing(4, after: titleLabel) - stackView + primaryStackView .pinTop() .pinLeading() .pinTrailing(0, .defaultHigh) diff --git a/VDS/Components/TextFields/InputField/FieldTypes/FieldType.swift b/VDS/Components/TextFields/InputField/FieldTypes/FieldType.swift index db45d767..ebecb17d 100644 --- a/VDS/Components/TextFields/InputField/FieldTypes/FieldType.swift +++ b/VDS/Components/TextFields/InputField/FieldTypes/FieldType.swift @@ -80,14 +80,6 @@ extension InputField { inputField.fieldStackView.setCustomSpacing(0, after: inputField.statusIcon) } - //set the width constraints - let maxwidth = inputField.frame.size.width - if let width = inputField.width, width > minWidth && width < maxwidth { - inputField.widthConstraint?.constant = width - } else { - inputField.widthConstraint?.constant = maxwidth >= minWidth ? maxwidth : minWidth - } - //placeholder inputField.textField.placeholder = placeholderText diff --git a/VDS/Components/TextFields/InputField/InputField.swift b/VDS/Components/TextFields/InputField/InputField.swift index e11d1253..08af1134 100644 --- a/VDS/Components/TextFields/InputField/InputField.swift +++ b/VDS/Components/TextFields/InputField/InputField.swift @@ -36,6 +36,17 @@ open class InputField: EntryFieldBase { //-------------------------------------------------- internal var titleLabelWidthConstraint: NSLayoutConstraint? + /// The is used for the for adding the helperLabel to the right of the containerView. + internal var horizontalStackView: UIStackView = { + return UIStackView().with { + $0.translatesAutoresizingMaskIntoConstraints = false + $0.axis = .horizontal + $0.distribution = .fillEqually + $0.spacing = VDSLayout.space3X + $0.alignment = .top + } + }() + //-------------------------------------------------- // MARK: - Public FieldType Properties //-------------------------------------------------- @@ -173,8 +184,8 @@ open class InputField: EntryFieldBase { textField.heightAnchor.constraint(equalToConstant: 20).isActive = true textField.delegate = self - stackView.addArrangedSubview(successLabel) - stackView.setCustomSpacing(8, after: successLabel) + primaryStackView.addArrangedSubview(successLabel) + primaryStackView.setCustomSpacing(8, after: successLabel) fieldStackView.addArrangedSubview(actionTextLink) @@ -248,6 +259,7 @@ open class InputField: EntryFieldBase { } open override func updateHelperLabel(){ //remove first + secondaryStackView.removeFromSuperview() helperLabel.removeFromSuperview() super.updateHelperLabel() @@ -255,14 +267,25 @@ open class InputField: EntryFieldBase { //set the helper label position if helperText != nil { if helperTextPlacement == .right { - middleStackView.spacing = VDSLayout.space3X - middleStackView.distribution = .fillEqually - middleStackView.addArrangedSubview(helperLabel) + horizontalStackView.addArrangedSubview(secondaryStackView) + horizontalStackView.addArrangedSubview(helperLabel) + primaryStackView.addArrangedSubview(horizontalStackView) } else { - middleStackView.spacing = 0 - middleStackView.distribution = .fill bottomContainerStackView.addArrangedSubview(helperLabel) + primaryStackView.addArrangedSubview(secondaryStackView) } + } else { + primaryStackView.addArrangedSubview(secondaryStackView) + } + + //set the width constraints + let minWidth = fieldType.handler().minWidth + let frameWidth = frame.size.width + let maxwidth = helperTextPlacement == .right ? (frameWidth - horizontalStackView.spacing) / 2 : frameWidth + if let width, width > minWidth && width < maxwidth { + widthConstraint?.constant = width + } else { + widthConstraint?.constant = maxwidth >= minWidth ? maxwidth : minWidth } } From 3ab8cec7a42e9e8e232054849bc8016d43c418ad Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Mon, 13 May 2024 11:26:12 -0500 Subject: [PATCH 27/46] added more documentation Signed-off-by: Matt Bruce --- VDS/Components/TextFields/EntryFieldBase.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/VDS/Components/TextFields/EntryFieldBase.swift b/VDS/Components/TextFields/EntryFieldBase.swift index 6dca8a0e..86cc6a48 100644 --- a/VDS/Components/TextFields/EntryFieldBase.swift +++ b/VDS/Components/TextFields/EntryFieldBase.swift @@ -49,6 +49,8 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { } }() + /// This is the veritcal stack view that has 2 rows, the containerView and the return view + /// of the getBottomContainer() method, by default returns the bottomContainerStackView. internal let secondaryStackView = UIStackView().with { $0.translatesAutoresizingMaskIntoConstraints = false $0.axis = .vertical @@ -56,6 +58,7 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { } /// This is the view that will be wrapped with the border for userInteraction. + /// The only subview of this view is the fieldStackView internal var containerView: UIView = { return UIView().with { $0.translatesAutoresizingMaskIntoConstraints = false From 4b5bbd1ace69cd800b9c1cfd975b9c785f470cb3 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Mon, 13 May 2024 12:16:18 -0500 Subject: [PATCH 28/46] got picker working again Signed-off-by: Matt Bruce --- VDS.xcodeproj/project.pbxproj | 8 +- VDS/Components/DatePicker/DatePicker.swift | 183 +++++++++++++++++++++ 2 files changed, 187 insertions(+), 4 deletions(-) create mode 100644 VDS/Components/DatePicker/DatePicker.swift diff --git a/VDS.xcodeproj/project.pbxproj b/VDS.xcodeproj/project.pbxproj index d04570b2..a8befef3 100644 --- a/VDS.xcodeproj/project.pbxproj +++ b/VDS.xcodeproj/project.pbxproj @@ -150,7 +150,7 @@ EAC58C142BED0DEC00BA39FA /* Number.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC58C132BED0DEC00BA39FA /* Number.swift */; }; EAC58C162BED0E0300BA39FA /* InlineAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC58C152BED0E0300BA39FA /* InlineAction.swift */; }; EAC58C182BED0E2300BA39FA /* SecurityCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC58C172BED0E2300BA39FA /* SecurityCode.swift */; }; - EAC58C212BF127FE00BA39FA /* DatePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC58C202BF127FE00BA39FA /* DatePicker.swift */; }; + EAC58C232BF2824200BA39FA /* DatePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC58C222BF2824200BA39FA /* DatePicker.swift */; }; EAC71A1D2A2E155A00E47A9F /* Checkbox.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC71A1C2A2E155A00E47A9F /* Checkbox.swift */; }; EAC71A1F2A2E173D00E47A9F /* RadioButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC71A1E2A2E173D00E47A9F /* RadioButton.swift */; }; EAC846F3294B95CE00F685BA /* ButtonGroupCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC846F2294B95CE00F685BA /* ButtonGroupCollectionViewCell.swift */; }; @@ -356,7 +356,7 @@ EAC58C132BED0DEC00BA39FA /* Number.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Number.swift; sourceTree = ""; }; EAC58C152BED0E0300BA39FA /* InlineAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InlineAction.swift; sourceTree = ""; }; EAC58C172BED0E2300BA39FA /* SecurityCode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecurityCode.swift; sourceTree = ""; }; - EAC58C202BF127FE00BA39FA /* DatePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = DatePicker.swift; path = ../../../../../../../../../Downloads/DatePicker.swift; sourceTree = ""; }; + EAC58C222BF2824200BA39FA /* DatePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatePicker.swift; sourceTree = ""; }; EAC71A1C2A2E155A00E47A9F /* Checkbox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Checkbox.swift; sourceTree = ""; }; EAC71A1E2A2E173D00E47A9F /* RadioButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioButton.swift; sourceTree = ""; }; EAC846F2294B95CE00F685BA /* ButtonGroupCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonGroupCollectionViewCell.swift; sourceTree = ""; }; @@ -915,7 +915,7 @@ EAC58C1F2BF127F000BA39FA /* DatePicker */ = { isa = PBXGroup; children = ( - EAC58C202BF127FE00BA39FA /* DatePicker.swift */, + EAC58C222BF2824200BA39FA /* DatePicker.swift */, ); path = DatePicker; sourceTree = ""; @@ -1168,7 +1168,7 @@ 1832AC572BA0791D008AE476 /* BreadcrumbCellItem.swift in Sources */, EA3361C328902D960071C351 /* Toggle.swift in Sources */, EAF7F0A0289AB7EC00B287F5 /* View.swift in Sources */, - EAC58C212BF127FE00BA39FA /* DatePicker.swift in Sources */, + EAC58C232BF2824200BA39FA /* DatePicker.swift in Sources */, EA89201328B568D8006B9984 /* RadioBoxItem.swift in Sources */, 71FC86E42B9841AC00700965 /* PaginationFlowLayout.swift in Sources */, EAC9258C2911C9DE00091998 /* InputField.swift in Sources */, diff --git a/VDS/Components/DatePicker/DatePicker.swift b/VDS/Components/DatePicker/DatePicker.swift new file mode 100644 index 00000000..9d049613 --- /dev/null +++ b/VDS/Components/DatePicker/DatePicker.swift @@ -0,0 +1,183 @@ +import Foundation +import UIKit +import VDSTokens +import Combine + +/// A dropdown select is an expandable menu of predefined options that allows a customer to make a single selection. +@objc(VDSDatePicker) +open class DatePicker: EntryFieldBase { + //-------------------------------------------------- + // 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) + } + + //-------------------------------------------------- + // MARK: - Public Properties + //-------------------------------------------------- + /// A callback when the selected option changes. Passes parameters (option). + open var onDateSelected: ((Date, DatePicker) -> Void)? + + //-------------------------------------------------- + // MARK: - Private Properties + //-------------------------------------------------- + internal var minWidthDefault = 186.0 + + internal let picker = UIDatePicker() + + internal var bottomStackView: UIStackView = { + return UIStackView().with { + $0.translatesAutoresizingMaskIntoConstraints = false + $0.axis = .vertical + $0.distribution = .fill + $0.alignment = .top + $0.spacing = VDSLayout.space2X + } + }() + + //-------------------------------------------------- + // MARK: - Public Properties + //-------------------------------------------------- + open var calendarIcon = Icon().with { + $0.name = .calendar + $0.size = .medium + } + + open var selectedDate: Date? { didSet { setNeedsUpdate() } } + + open override var value: String? { + get { selectedDateLabel.text } + set { } + } + + open var selectedDateLabel = Label().with { + $0.setContentCompressionResistancePriority(.required, for: .vertical) + $0.textAlignment = .left + $0.textStyle = .bodyLarge + $0.lineBreakMode = .byCharWrapping + } + + public enum DateFormat: String, CaseIterable { + case shortNumeric + case longAlphabetic + case mediumNumeric + case consiseNumeric + + public var format: String { + switch self { + case .shortNumeric: "MM/dd/yy" + case .longAlphabetic: "MMMM d, yyyy" + case .mediumNumeric: "MM/dd/yyyy" + case .consiseNumeric: "M/d/yyyy" + } + } + } + + open var dateFormat: DateFormat = .shortNumeric { didSet{ setNeedsUpdate() } } + + //-------------------------------------------------- + // MARK: - Configuration Properties + //-------------------------------------------------- + internal override var containerSize: CGSize { CGSize(width: minWidthDefault, height: 44) } + + //-------------------------------------------------- + // MARK: - Overrides + //-------------------------------------------------- + + /// Called once when a view is initialized and is used to Setup additional UI or other constants and configurations. + open override func setup() { + super.setup() + + accessibilityLabel = "Dropdown Select" + + // setting color config + selectedDateLabel.textColorConfiguration = primaryColorConfiguration.eraseToAnyColorable() + + // setup the calendar + picker.datePickerMode = .date + picker.preferredDatePickerStyle = .inline + picker.addTarget(self, action: #selector(dateChanged(_:)), for: .valueChanged) + picker.isHidden = true + + // tap gesture + fieldStackView + .publisher(for: UITapGestureRecognizer()) + .sink { [weak self] _ in + self?.togglePicker() + } + .store(in: &subscribers) + } + + open override func getFieldContainer() -> UIView { + // stackview for controls in EntryFieldBase.controlContainerView + let controlStackView = UIStackView().with { + $0.translatesAutoresizingMaskIntoConstraints = false + $0.axis = .horizontal + $0.spacing = VDSLayout.space3X + } + controlStackView.addArrangedSubview(calendarIcon) + controlStackView.addArrangedSubview(selectedDateLabel) + return controlStackView + } + + open override func getBottomContainer() -> UIView { + bottomStackView.addArrangedSubview(bottomContainerStackView) + bottomStackView.addArrangedSubview(picker) + return bottomStackView + } + + /// Used to make changes to the View based off a change events or from local properties. + open override func updateView() { + super.updateView() + + if let selectedDate { + picker.date = selectedDate + formatDate(selectedDate) + } + + selectedDateLabel.surface = surface + selectedDateLabel.isEnabled = isEnabled + calendarIcon.color = iconColorConfiguration.getColor(self) + + //set the width constraints + let minWidth = containerSize.width + let maxwidth = frame.size.width + if let width, width > minWidth && width < maxwidth { + widthConstraint?.constant = width + } else { + widthConstraint?.constant = maxwidth >= minWidth ? maxwidth : minWidth + } + } + + /// Resets to default settings. + open override func reset() { + super.reset() + selectedDateLabel.textStyle = .bodyLarge + } + + internal func formatDate(_ date: Date) { + let formatter = DateFormatter() + formatter.dateFormat = dateFormat.format + selectedDateLabel.text = formatter.string(from: date) + } + + internal func togglePicker() { + picker.isHidden = !picker.isHidden + bottomContainerStackView.isHidden = !bottomContainerStackView.isHidden + } + + @objc private func dateChanged(_ sender: UIDatePicker) { + selectedDate = sender.date + sendActions(for: .valueChanged) + togglePicker() + } +} From e667054eb636df3b5d07a68e4a048179fdcc0540 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Mon, 13 May 2024 12:32:46 -0500 Subject: [PATCH 29/46] refactored calculating width Signed-off-by: Matt Bruce --- VDS/Components/DatePicker/DatePicker.swift | 9 --------- .../DropdownSelect/DropdownSelect.swift | 20 ++++++++----------- .../TextFields/EntryFieldBase.swift | 16 +++++++++++++-- .../TextFields/InputField/InputField.swift | 15 +++++--------- .../TextFields/TextArea/TextArea.swift | 4 ---- 5 files changed, 27 insertions(+), 37 deletions(-) diff --git a/VDS/Components/DatePicker/DatePicker.swift b/VDS/Components/DatePicker/DatePicker.swift index 9d049613..a7dc3e29 100644 --- a/VDS/Components/DatePicker/DatePicker.swift +++ b/VDS/Components/DatePicker/DatePicker.swift @@ -147,15 +147,6 @@ open class DatePicker: EntryFieldBase { selectedDateLabel.surface = surface selectedDateLabel.isEnabled = isEnabled calendarIcon.color = iconColorConfiguration.getColor(self) - - //set the width constraints - let minWidth = containerSize.width - let maxwidth = frame.size.width - if let width, width > minWidth && width < maxwidth { - widthConstraint?.constant = width - } else { - widthConstraint?.constant = maxwidth >= minWidth ? maxwidth : minWidth - } } /// Resets to default settings. diff --git a/VDS/Components/DropdownSelect/DropdownSelect.swift b/VDS/Components/DropdownSelect/DropdownSelect.swift index 411f8327..9c30ea03 100644 --- a/VDS/Components/DropdownSelect/DropdownSelect.swift +++ b/VDS/Components/DropdownSelect/DropdownSelect.swift @@ -71,7 +71,11 @@ open class DropdownSelect: EntryFieldBase { //-------------------------------------------------- internal var minWidthDefault = 66.0 internal var minWidthInlineLabel = 102.0 - internal var minWidth: CGFloat { showInlineLabel ? minWidthInlineLabel : minWidthDefault } + internal override var minWidth: CGFloat { showInlineLabel ? minWidthInlineLabel : minWidthDefault } + internal override var maxWidth: CGFloat { + let frameWidth = frame.size.width + return helperTextPlacement == .right ? (frameWidth - horizontalStackView.spacing) / 2 : frameWidth + } /// The is used for the for adding the helperLabel to the right of the containerView. internal var horizontalStackView: UIStackView = { @@ -133,7 +137,8 @@ open class DropdownSelect: EntryFieldBase { /// Called once when a view is initialized and is used to Setup additional UI or other constants and configurations. open override func setup() { super.setup() - + widthConstraint?.activate() + titleLabel.setContentCompressionResistancePriority(.required, for: .horizontal) titleLabel.setContentHuggingPriority(.required, for: .horizontal) titleLabelWidthConstraint = titleLabel.width(constant: 0) @@ -302,17 +307,8 @@ open class DropdownSelect: EntryFieldBase { } else { primaryStackView.addArrangedSubview(secondaryStackView) } - - //set the width constraints - let frameWidth = frame.size.width - let maxwidth = helperTextPlacement == .right ? (frameWidth - horizontalStackView.spacing) / 2 : frameWidth - if let width, width > minWidth && width < maxwidth { - widthConstraint?.constant = width - } else { - widthConstraint?.constant = maxwidth >= minWidth ? maxwidth : minWidth - } } - + open override func updateAccessibility() { super.updateAccessibility() let selectedOption = selectedOptionLabel.text ?? "" diff --git a/VDS/Components/TextFields/EntryFieldBase.swift b/VDS/Components/TextFields/EntryFieldBase.swift index 86cc6a48..c7cd766c 100644 --- a/VDS/Components/TextFields/EntryFieldBase.swift +++ b/VDS/Components/TextFields/EntryFieldBase.swift @@ -93,7 +93,9 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { // MARK: - Configuration Properties //-------------------------------------------------- // Sizes are from InVision design specs. - internal var containerSize: CGSize { CGSize(width: 45, height: 44) } + internal var maxWidth: CGFloat { frame.size.width } + internal var minWidth: CGFloat { containerSize.width } + internal var containerSize: CGSize { CGSize(width: minWidth, height: 44) } internal let primaryColorConfiguration = ViewColorConfiguration().with { $0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forDisabled: true) @@ -216,7 +218,7 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { //create the wrapping view heightConstraint = containerView.heightGreaterThanEqualTo(constant: containerSize.height) - widthConstraint = containerView.width(constant: 0) + widthConstraint = containerView.width(constant: frame.size.width) secondaryStackView.addArrangedSubview(containerView) secondaryStackView.setCustomSpacing(8, after: containerView) @@ -292,6 +294,7 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { updateTitleLabel() updateErrorLabel() updateHelperLabel() + updateContainerWidth() } open func validate(){ @@ -314,6 +317,15 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { //-------------------------------------------------- // MARK: - Public Methods //-------------------------------------------------- + open func updateContainerWidth() { + if let width, width > minWidth && width < maxWidth { + widthConstraint?.constant = width + } else { + widthConstraint?.constant = maxWidth >= minWidth ? maxWidth : minWidth + } + widthConstraint?.activate() + } + /// Container for the area in which the user interacts. open func getFieldContainer() -> UIView { fatalError("Subclass must return the view that contains the field/view the user will interact with.") diff --git a/VDS/Components/TextFields/InputField/InputField.swift b/VDS/Components/TextFields/InputField/InputField.swift index 08af1134..1886a9ab 100644 --- a/VDS/Components/TextFields/InputField/InputField.swift +++ b/VDS/Components/TextFields/InputField/InputField.swift @@ -35,6 +35,11 @@ open class InputField: EntryFieldBase { // MARK: - Private Properties //-------------------------------------------------- internal var titleLabelWidthConstraint: NSLayoutConstraint? + internal override var minWidth: CGFloat { fieldType.handler().minWidth } + internal override var maxWidth: CGFloat { + let frameWidth = frame.size.width + return helperTextPlacement == .right ? (frameWidth - horizontalStackView.spacing) / 2 : frameWidth + } /// The is used for the for adding the helperLabel to the right of the containerView. internal var horizontalStackView: UIStackView = { @@ -277,16 +282,6 @@ open class InputField: EntryFieldBase { } else { primaryStackView.addArrangedSubview(secondaryStackView) } - - //set the width constraints - let minWidth = fieldType.handler().minWidth - let frameWidth = frame.size.width - let maxwidth = helperTextPlacement == .right ? (frameWidth - horizontalStackView.spacing) / 2 : frameWidth - if let width, width > minWidth && width < maxwidth { - widthConstraint?.constant = width - } else { - widthConstraint?.constant = maxwidth >= minWidth ? maxwidth : minWidth - } } override func updateRules() { diff --git a/VDS/Components/TextFields/TextArea/TextArea.swift b/VDS/Components/TextFields/TextArea/TextArea.swift index 3913f2e6..eccd228e 100644 --- a/VDS/Components/TextFields/TextArea/TextArea.swift +++ b/VDS/Components/TextFields/TextArea/TextArea.swift @@ -187,10 +187,6 @@ open class TextArea: EntryFieldBase { textView.isEnabled = isEnabled textView.surface = surface - //set the width constraints - let maxwidth = frame.size.width - let minWidth = containerSize.width - widthConstraint?.constant = maxwidth >= minWidth ? maxwidth : minWidth textViewHeightConstraint?.constant = minHeight.value characterCounterLabel.text = getCharacterCounterText() From 28d8161d09aea73fb93093d5af428682073d9fa2 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Mon, 13 May 2024 13:12:07 -0500 Subject: [PATCH 30/46] refactored date picker a popover Signed-off-by: Matt Bruce --- VDS/Components/DatePicker/DatePicker.swift | 85 ++++++++++++++++------ 1 file changed, 64 insertions(+), 21 deletions(-) diff --git a/VDS/Components/DatePicker/DatePicker.swift b/VDS/Components/DatePicker/DatePicker.swift index a7dc3e29..cd4b3777 100644 --- a/VDS/Components/DatePicker/DatePicker.swift +++ b/VDS/Components/DatePicker/DatePicker.swift @@ -5,7 +5,7 @@ import Combine /// A dropdown select is an expandable menu of predefined options that allows a customer to make a single selection. @objc(VDSDatePicker) -open class DatePicker: EntryFieldBase { +open class DatePicker: EntryFieldBase, DatePickerPopoverViewControllerDelegate, UIPopoverPresentationControllerDelegate { //-------------------------------------------------- // MARK: - Initializers //-------------------------------------------------- @@ -31,9 +31,7 @@ open class DatePicker: EntryFieldBase { // MARK: - Private Properties //-------------------------------------------------- internal var minWidthDefault = 186.0 - - internal let picker = UIDatePicker() - + internal var bottomStackView: UIStackView = { return UIStackView().with { $0.translatesAutoresizingMaskIntoConstraints = false @@ -103,10 +101,6 @@ open class DatePicker: EntryFieldBase { selectedDateLabel.textColorConfiguration = primaryColorConfiguration.eraseToAnyColorable() // setup the calendar - picker.datePickerMode = .date - picker.preferredDatePickerStyle = .inline - picker.addTarget(self, action: #selector(dateChanged(_:)), for: .valueChanged) - picker.isHidden = true // tap gesture fieldStackView @@ -128,19 +122,12 @@ open class DatePicker: EntryFieldBase { controlStackView.addArrangedSubview(selectedDateLabel) return controlStackView } - - open override func getBottomContainer() -> UIView { - bottomStackView.addArrangedSubview(bottomContainerStackView) - bottomStackView.addArrangedSubview(picker) - return bottomStackView - } - + /// Used to make changes to the View based off a change events or from local properties. open override func updateView() { super.updateView() if let selectedDate { - picker.date = selectedDate formatDate(selectedDate) } @@ -162,13 +149,69 @@ open class DatePicker: EntryFieldBase { } internal func togglePicker() { - picker.isHidden = !picker.isHidden - bottomContainerStackView.isHidden = !bottomContainerStackView.isHidden + let calendarVC = DatePickerPopoverViewController(calendar: Calendar(identifier: .gregorian), delegate: self) + calendarVC.modalPresentationStyle = .popover + calendarVC.selectedDate = selectedDate ?? Date() + if let popoverController = calendarVC.popoverPresentationController { + popoverController.delegate = self + popoverController.sourceView = containerView + popoverController.sourceRect = containerView.bounds + popoverController.permittedArrowDirections = .any + } + if let viewController = UIApplication.topViewController() { + viewController.present(calendarVC, animated: true, completion: nil) + } } - @objc private func dateChanged(_ sender: UIDatePicker) { - selectedDate = sender.date + internal func didSelectDate(_ controller: DatePickerPopoverViewController, date: Date) { + selectedDate = date + controller.dismiss(animated: true) sendActions(for: .valueChanged) - togglePicker() + } + + public func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle { + return .none + } +} + +protocol DatePickerPopoverViewControllerDelegate: NSObject { + func didSelectDate(_ controller: DatePickerPopoverViewController, date: Date) +} + +class DatePickerPopoverViewController: UIViewController { + + private let picker = UIDatePicker() + weak var delegate: DatePickerPopoverViewControllerDelegate? + + init(calendar: Calendar, delegate: DatePickerPopoverViewControllerDelegate?) { + self.delegate = delegate + super.init(nibName: nil, bundle: nil) + picker.datePickerMode = .date + picker.preferredDatePickerStyle = .inline + picker.addTarget(self, action: #selector(dateChanged(_:)), for: .valueChanged) + } + + var selectedDate: Date = Date() { + didSet { + picker.date = selectedDate + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func loadView() { + view = picker + view.backgroundColor = .white + } + + override func viewDidLoad() { + super.viewDidLoad() + preferredContentSize = CGSize(width: 300, height: 400) // Adjust as needed + } + + @objc private func dateChanged(_ sender: UIDatePicker) { + delegate?.didSelectDate(self, date: sender.date) } } From 31b893042c5a26843f53384e3bf88bbf4c2369c1 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Mon, 13 May 2024 13:15:46 -0500 Subject: [PATCH 31/46] updated for isReadOnly Signed-off-by: Matt Bruce --- VDS/Components/DatePicker/DatePicker.swift | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/VDS/Components/DatePicker/DatePicker.swift b/VDS/Components/DatePicker/DatePicker.swift index cd4b3777..96919781 100644 --- a/VDS/Components/DatePicker/DatePicker.swift +++ b/VDS/Components/DatePicker/DatePicker.swift @@ -100,13 +100,14 @@ open class DatePicker: EntryFieldBase, DatePickerPopoverViewControllerDelegate, // setting color config selectedDateLabel.textColorConfiguration = primaryColorConfiguration.eraseToAnyColorable() - // setup the calendar - // tap gesture fieldStackView .publisher(for: UITapGestureRecognizer()) .sink { [weak self] _ in - self?.togglePicker() + guard let self else { return } + if self.isEnabled && !self.isReadOnly { + self.togglePicker() + } } .store(in: &subscribers) } From 1c5273155df9f03c19daecf709ebd42afe62751c Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Mon, 13 May 2024 15:13:24 -0500 Subject: [PATCH 32/46] added changelog Signed-off-by: Matt Bruce --- VDS.xcodeproj/project.pbxproj | 4 ++ VDS/Components/DatePicker/DatePicker.swift | 6 ++- .../DatePicker/DatePickerChangeLog.txt | 48 +++++++++++++++++++ 3 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 VDS/Components/DatePicker/DatePickerChangeLog.txt diff --git a/VDS.xcodeproj/project.pbxproj b/VDS.xcodeproj/project.pbxproj index a8befef3..33d4b421 100644 --- a/VDS.xcodeproj/project.pbxproj +++ b/VDS.xcodeproj/project.pbxproj @@ -151,6 +151,7 @@ EAC58C162BED0E0300BA39FA /* InlineAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC58C152BED0E0300BA39FA /* InlineAction.swift */; }; EAC58C182BED0E2300BA39FA /* SecurityCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC58C172BED0E2300BA39FA /* SecurityCode.swift */; }; EAC58C232BF2824200BA39FA /* DatePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC58C222BF2824200BA39FA /* DatePicker.swift */; }; + EAC58C252BF2A7FB00BA39FA /* DatePickerChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = EAC58C242BF2A7FB00BA39FA /* DatePickerChangeLog.txt */; }; EAC71A1D2A2E155A00E47A9F /* Checkbox.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC71A1C2A2E155A00E47A9F /* Checkbox.swift */; }; EAC71A1F2A2E173D00E47A9F /* RadioButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC71A1E2A2E173D00E47A9F /* RadioButton.swift */; }; EAC846F3294B95CE00F685BA /* ButtonGroupCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC846F2294B95CE00F685BA /* ButtonGroupCollectionViewCell.swift */; }; @@ -357,6 +358,7 @@ EAC58C152BED0E0300BA39FA /* InlineAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InlineAction.swift; sourceTree = ""; }; EAC58C172BED0E2300BA39FA /* SecurityCode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecurityCode.swift; sourceTree = ""; }; EAC58C222BF2824200BA39FA /* DatePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatePicker.swift; sourceTree = ""; }; + EAC58C242BF2A7FB00BA39FA /* DatePickerChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = DatePickerChangeLog.txt; sourceTree = ""; }; EAC71A1C2A2E155A00E47A9F /* Checkbox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Checkbox.swift; sourceTree = ""; }; EAC71A1E2A2E173D00E47A9F /* RadioButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioButton.swift; sourceTree = ""; }; EAC846F2294B95CE00F685BA /* ButtonGroupCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonGroupCollectionViewCell.swift; sourceTree = ""; }; @@ -916,6 +918,7 @@ isa = PBXGroup; children = ( EAC58C222BF2824200BA39FA /* DatePicker.swift */, + EAC58C242BF2A7FB00BA39FA /* DatePickerChangeLog.txt */, ); path = DatePicker; sourceTree = ""; @@ -1136,6 +1139,7 @@ 186D13CF2BBC36EF00986B53 /* DropdownSelectChangeLog.txt in Resources */, EAEEEC9C2B1F8F0700531FC2 /* TextLinkCaretChangeLog.txt in Resources */, EAA5EEE428F5B855003B3210 /* VerizonNHGDS-Light.otf in Resources */, + EAC58C252BF2A7FB00BA39FA /* DatePickerChangeLog.txt in Resources */, 71B5FCBB2B95A0CA00269BCC /* PaginationChangeLog.txt in Resources */, EAEEECAD2B1FC1A600531FC2 /* TitleLockupChangeLog.txt in Resources */, EAEEECAB2B1FBF2A00531FC2 /* ToggleChangeLog.txt in Resources */, diff --git a/VDS/Components/DatePicker/DatePicker.swift b/VDS/Components/DatePicker/DatePicker.swift index 96919781..2d6b620e 100644 --- a/VDS/Components/DatePicker/DatePicker.swift +++ b/VDS/Components/DatePicker/DatePicker.swift @@ -64,7 +64,7 @@ open class DatePicker: EntryFieldBase, DatePickerPopoverViewControllerDelegate, $0.lineBreakMode = .byCharWrapping } - public enum DateFormat: String, CaseIterable { + public enum DateFormat: String, CaseIterable, CustomStringConvertible { case shortNumeric case longAlphabetic case mediumNumeric @@ -78,6 +78,10 @@ open class DatePicker: EntryFieldBase, DatePickerPopoverViewControllerDelegate, case .consiseNumeric: "M/d/yyyy" } } + + public var description: String { + return format + } } open var dateFormat: DateFormat = .shortNumeric { didSet{ setNeedsUpdate() } } diff --git a/VDS/Components/DatePicker/DatePickerChangeLog.txt b/VDS/Components/DatePicker/DatePickerChangeLog.txt new file mode 100644 index 00000000..e588b709 --- /dev/null +++ b/VDS/Components/DatePicker/DatePickerChangeLog.txt @@ -0,0 +1,48 @@ +MM/DD/YYYY +---------------- +Initial Brand 3.0 handoff + +01/06/2021 +---------------- +Removed Max Width, Increased Min width. +Updated the tokens with FormControl tokens + +02/24/2022 +---------------- +Replaced Calendar, Info, Error, Caret Left, and Caret Right Non-Scaling icons with VDS Icon. + +06/22/2022 +---------------- +Updated Calendar instances with new Calendar (with Indicators) + +07/27/2022 +---------------- +Updated Calendar Date Picker Configurations to include Background Transparent Boolean. +Moved Default Selection Date, Always opened and Date formats to configurartions page. + +08/04/2022 +---------------- +Updated default and inverted prop to light and dark surface. + +09/09/2022 +---------------- +Added missing helper text from disabled state visuals. + +12/13/2022 +---------------- +Updated supported date formats +Added "(web only)" to any instance of "keyboard focus" +Replaced form border and focus border pixel values, styles & spacing with tokens. + +01/20/2023 +---------------- +Updated Specs to use new SPEC Templates and SPEC DOC Components. +Tweaked written out date format so Mo becomes Mon + +04/12/2023 +---------------- +Updated hex colors for updated feedback tokens in error states. + +02/08/2024 +---------------- +Added Calendar position section to Behaviors. From 8fb424bed9b66070b12d0ed829a32663ab9309de Mon Sep 17 00:00:00 2001 From: vasavk Date: Tue, 14 May 2024 15:23:07 +0530 Subject: [PATCH 33/46] Digital ACT-191 ONEAPP-7958 story: added active state to highlight date in selecting date --- VDS/Components/Calendar/Calendar.swift | 12 ++++++++++++ .../Calendar/CalendarDateViewCell.swift | 17 +++++++++++++++-- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/VDS/Components/Calendar/Calendar.swift b/VDS/Components/Calendar/Calendar.swift index 9ec8e6fc..ff137004 100644 --- a/VDS/Components/Calendar/Calendar.swift +++ b/VDS/Components/Calendar/Calendar.swift @@ -296,11 +296,23 @@ extension CalendarBase: UICollectionViewDelegate, UICollectionViewDataSource, UI return UICollectionReusableView() } + + public func collectionView(_ collectionView: UICollectionView, shouldHighlightItemAt indexPath: IndexPath) -> Bool { + if let cell = collectionView.cellForItem(at: indexPath) as? CalendarDateViewCell { + let isEnabled: Bool = cell.isDateEnabled() + if isEnabled { + cell.activeModeStart() + } + } + return true + } + public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { // reload selected index, if it is in enabled state. if let cell = collectionView.cellForItem(at: indexPath) as? CalendarDateViewCell { let isEnabled: Bool = cell.isDateEnabled() if isEnabled { + cell.activeModeEnd() // Callback to pass selected date if it is enabled only. let selectedItem = Calendar.current.date(byAdding: .day, value: 1, to: self.dates[indexPath.row])! onChangeSelectedDate?(selectedItem) diff --git a/VDS/Components/Calendar/CalendarDateViewCell.swift b/VDS/Components/Calendar/CalendarDateViewCell.swift index 26886d0a..29bcb730 100644 --- a/VDS/Components/Calendar/CalendarDateViewCell.swift +++ b/VDS/Components/Calendar/CalendarDateViewCell.swift @@ -42,7 +42,7 @@ final class CalendarDateViewCell: UICollectionViewCell { } private lazy var shapeLayer = CAShapeLayer() - + private var surface: Surface = .light private let selectedTextColorConfiguration = SurfaceColorConfiguration(VDSColor.elementsPrimaryInverseOnlight, VDSColor.elementsPrimaryInverseOndark) private let selectedBackgroundColor = SurfaceColorConfiguration(VDSColor.backgroundPrimaryInverseLight, VDSColor.backgroundPrimaryInverseDark) private let selectedCellIndicatorColorConfiguration = SurfaceColorConfiguration(VDSColor.paletteGray65, VDSColor.paletteGray44) @@ -50,7 +50,7 @@ final class CalendarDateViewCell: UICollectionViewCell { private let unselectedCellIndicatorColorConfiguration = SurfaceColorConfiguration(VDSColor.elementsSecondaryOnlight, VDSColor.elementsSecondaryOndark) private let disabledTextColorConfiguration = SurfaceColorConfiguration(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark) private let disabledBackgroundColor = SurfaceColorConfiguration(VDSFormControlsColor.backgroundOnlight, VDSFormControlsColor.backgroundOndark) - + private var activeBorderColorConfiguration = SurfaceColorConfiguration(VDSFormControlsColor.borderHoverOnlight , VDSFormControlsColor.borderHoverOndark) private let currentDate = Date() //-------------------------------------------------- @@ -100,6 +100,7 @@ final class CalendarDateViewCell: UICollectionViewCell { stackView.arrangedSubviews.forEach { $0.removeFromSuperview() } numberLabel.surface = surface numberLabel.text = text + self.surface = surface // enable/disable cells based on min date, max date and active/inactive dates. self.updateLabel(with:surface, displayDate: displayDate, minDate: minDate, maxDate: maxDate, activeDates: activeDates, inactiveDates: inactiveDates) @@ -152,6 +153,18 @@ final class CalendarDateViewCell: UICollectionViewCell { return numberLabel.isEnabled } + func activeModeStart() { + numberLabel.layer.borderColor = activeBorderColorConfiguration.getColor(surface).cgColor + numberLabel.layer.borderWidth = VDSFormControls.borderWidth + numberLabel.layer.cornerRadius = VDSFormControls.borderRadius + } + + func activeModeEnd() { + numberLabel.layer.borderColor = nil + numberLabel.layer.borderWidth = 0 + numberLabel.layer.cornerRadius = 0 + } + func disableLabel(with surface: Surface) { numberLabel.isEnabled = false numberLabel.textColor = disabledTextColorConfiguration.getColor(surface) From f30d416164903dba1ac1658e3fbf5d67d7663394 Mon Sep 17 00:00:00 2001 From: vasavk Date: Tue, 14 May 2024 15:24:51 +0530 Subject: [PATCH 34/46] Digital ACT-191 ONEAPP-7958 story: adding a border to the collectionView instead of the CalendarBase.layer --- VDS/Components/Calendar/Calendar.swift | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/VDS/Components/Calendar/Calendar.swift b/VDS/Components/Calendar/Calendar.swift index ff137004..d980d0b3 100644 --- a/VDS/Components/Calendar/Calendar.swift +++ b/VDS/Components/Calendar/Calendar.swift @@ -158,15 +158,15 @@ open class CalendarBase: View { self.fetchDates(with: displayDate) } - layer.backgroundColor = backgroundColorConfiguration.getColor(self).cgColor + containerView.layer.backgroundColor = backgroundColorConfiguration.getColor(self).cgColor if hideContainerBorder { - layer.borderColor = nil - layer.borderWidth = 0 - layer.cornerRadius = 0 + containerView.layer.borderColor = nil + containerView.layer.borderWidth = 0 + containerView.layer.cornerRadius = 0 } else { - layer.borderColor = containerBorderColorConfiguration.getColor(self).cgColor - layer.borderWidth = VDSFormControls.borderWidth - layer.cornerRadius = VDSFormControls.borderRadius + containerView.layer.borderColor = containerBorderColorConfiguration.getColor(self).cgColor + containerView.layer.borderWidth = VDSFormControls.borderWidth + containerView.layer.cornerRadius = VDSFormControls.borderRadius } } From a440ba48443bf00a18c5750609e0bea3b445076a Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 14 May 2024 09:00:21 -0500 Subject: [PATCH 35/46] added external link icon Signed-off-by: Matt Bruce --- .../external-link.imageset/Contents.json | 15 +++++++++++++++ .../external-link.imageset/external-link.svg | 1 + 2 files changed, 16 insertions(+) create mode 100644 VDS/SupportingFiles/Icons.xcassets/Restricted/external-link.imageset/Contents.json create mode 100644 VDS/SupportingFiles/Icons.xcassets/Restricted/external-link.imageset/external-link.svg diff --git a/VDS/SupportingFiles/Icons.xcassets/Restricted/external-link.imageset/Contents.json b/VDS/SupportingFiles/Icons.xcassets/Restricted/external-link.imageset/Contents.json new file mode 100644 index 00000000..1e10c5e2 --- /dev/null +++ b/VDS/SupportingFiles/Icons.xcassets/Restricted/external-link.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "external-link.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/VDS/SupportingFiles/Icons.xcassets/Restricted/external-link.imageset/external-link.svg b/VDS/SupportingFiles/Icons.xcassets/Restricted/external-link.imageset/external-link.svg new file mode 100644 index 00000000..5c545b6e --- /dev/null +++ b/VDS/SupportingFiles/Icons.xcassets/Restricted/external-link.imageset/external-link.svg @@ -0,0 +1 @@ + \ No newline at end of file From 05829907a9a5e68c5466126ae9b828f499d9149b Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 14 May 2024 09:00:36 -0500 Subject: [PATCH 36/46] updated icon models for color and icon type Signed-off-by: Matt Bruce --- .../Tilelet/TileletIconModels.swift | 34 ++++++++++++++++--- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/VDS/Components/Tilelet/TileletIconModels.swift b/VDS/Components/Tilelet/TileletIconModels.swift index 457cd907..788a2155 100644 --- a/VDS/Components/Tilelet/TileletIconModels.swift +++ b/VDS/Components/Tilelet/TileletIconModels.swift @@ -7,6 +7,7 @@ import Foundation import UIKit +import VDSTokens extension Tilelet { @@ -15,17 +16,21 @@ extension Tilelet { /// A representation that will be used to render the icon with corresponding name. public var name: Icon.Name + /// Color of the icon. + public var color: UIColor + /// Enum for a preset height and width for the icon. public var size: Icon.Size /// Accessible Text for the Icon - var accessibleText: String + public var accessibleText: String /// Current Surface and this is used to pass down to child objects that implement Surfacable public var surface: Surface - public init(name: Icon.Name = .multipleDocuments, size: Icon.Size = .medium, accessibleText: String? = nil, surface: Surface = .dark) { + public init(name: Icon.Name = .multipleDocuments, color: UIColor = VDSColor.paletteBlack, size: Icon.Size = .medium, accessibleText: String? = nil, surface: Surface = .dark) { self.name = name + self.color = color self.accessibleText = accessibleText ?? name.rawValue self.size = size self.surface = surface @@ -34,13 +39,34 @@ extension Tilelet { /// Model that represents the options available for the directional icon. public struct DirectionalIcon { + public enum IconType { + case rightArrow + case externalLink + + public var iconName: Icon.Name { + return self == .rightArrow ? .rightArrow : .externalLink + } + } + + /// Color of the icon. + public var color: UIColor + + /// Accessible Text for the Icon + public var accessibleText: String + + /// Enum for a icon type you want shown.. + public var iconType: IconType + /// Enum for a preset height and width for the icon. public var size: Icon.Size - + /// Current Surface and this is used to pass down to child objects that implement Surfacable public var surface: Surface - public init(size: Icon.Size = .medium, surface: Surface = .dark) { + public init(iconType: IconType = .rightArrow, color: UIColor = VDSColor.paletteBlack, size: Icon.Size = .medium, accessibleText: String? = nil, surface: Surface = .dark) { + self.iconType = iconType + self.color = color + self.accessibleText = accessibleText ?? iconType.iconName.rawValue self.size = size self.surface = surface } From b31ff5c9619e305c98c1a787406eb64f46cc8699 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 14 May 2024 09:00:54 -0500 Subject: [PATCH 37/46] added iconName for the new asset Signed-off-by: Matt Bruce --- VDS/Components/Icon/IconName.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/VDS/Components/Icon/IconName.swift b/VDS/Components/Icon/IconName.swift index 27f57787..6af40d3d 100644 --- a/VDS/Components/Icon/IconName.swift +++ b/VDS/Components/Icon/IconName.swift @@ -55,6 +55,7 @@ extension Icon { public static let downCaret = Name(name: "down-caret") public static let downCaretBold = Name(name: "down-caret-bold") public static let error = Name(name: "error") + public static let externalLink = Icon.Name(name: "external-link") public static let info = Name(name: "info") public static let multipleDocuments = Name(name: "multiple-documents") public static let leftArrow = Name(name: "left-arrow") @@ -62,5 +63,6 @@ extension Icon { public static let rightArrow = Name(name: "right-arrow") public static let rightCaret = Name(name: "right-caret") public static let warning = Name(name: "warning") + } } From 16a4bb28ace7bdbcb13ee342caab6c1729dbcfec Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 14 May 2024 09:01:04 -0500 Subject: [PATCH 38/46] updated model changes in tilelet Signed-off-by: Matt Bruce --- VDS/Components/Tilelet/Tilelet.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/VDS/Components/Tilelet/Tilelet.swift b/VDS/Components/Tilelet/Tilelet.swift index f0814e11..98ce49aa 100644 --- a/VDS/Components/Tilelet/Tilelet.swift +++ b/VDS/Components/Tilelet/Tilelet.swift @@ -532,6 +532,7 @@ open class Tilelet: TileContainerBase { var showIconContainerView = false if let descriptiveIconModel { descriptiveIcon.name = descriptiveIconModel.name + descriptiveIcon.color = descriptiveIconModel.color descriptiveIcon.size = descriptiveIconModel.size descriptiveIcon.surface = backgroundColorSurface descriptiveIcon.accessibilityLabel = descriptiveIconModel.accessibleText @@ -539,9 +540,11 @@ open class Tilelet: TileContainerBase { } if let directionalIconModel { + directionalIcon.name = directionalIconModel.iconType.iconName + directionalIcon.color = directionalIconModel.color directionalIcon.size = directionalIconModel.size directionalIcon.surface = backgroundColorSurface - directionalIcon.accessibilityLabel = "Right arrow" + directionalIcon.accessibilityLabel = directionalIconModel.accessibleText showIconContainerView = true } From 6f17c1b0dab09e4be8a272d7fbea6330ec30ce14 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 14 May 2024 11:23:00 -0500 Subject: [PATCH 39/46] refactored to use new calendar Signed-off-by: Matt Bruce --- VDS/Components/Calendar/Date+Extension.swift | 2 +- VDS/Components/DatePicker/DatePicker.swift | 195 +++++++++++++++---- 2 files changed, 159 insertions(+), 38 deletions(-) diff --git a/VDS/Components/Calendar/Date+Extension.swift b/VDS/Components/Calendar/Date+Extension.swift index 8d697049..ca4f5a3b 100644 --- a/VDS/Components/Calendar/Date+Extension.swift +++ b/VDS/Components/Calendar/Date+Extension.swift @@ -7,7 +7,7 @@ import Foundation -extension Date { +public extension Date { static var firstDayOfWeek = Calendar.current.firstWeekday diff --git a/VDS/Components/DatePicker/DatePicker.swift b/VDS/Components/DatePicker/DatePicker.swift index 2d6b620e..710de91a 100644 --- a/VDS/Components/DatePicker/DatePicker.swift +++ b/VDS/Components/DatePicker/DatePicker.swift @@ -52,6 +52,10 @@ open class DatePicker: EntryFieldBase, DatePickerPopoverViewControllerDelegate, open var selectedDate: Date? { didSet { setNeedsUpdate() } } + open var calendarModel: CalendarModel = .init() { + didSet { setNeedsUpdate() } + } + open override var value: String? { get { selectedDateLabel.text } set { } @@ -154,14 +158,15 @@ open class DatePicker: EntryFieldBase, DatePickerPopoverViewControllerDelegate, } internal func togglePicker() { - let calendarVC = DatePickerPopoverViewController(calendar: Calendar(identifier: .gregorian), delegate: self) + //let calendarVC = DatePickerPopoverViewController(calendar: Calendar(identifier: .gregorian), delegate: self) + let calendarVC = DatePickerPopoverViewController(calendarModel, delegate: self) calendarVC.modalPresentationStyle = .popover calendarVC.selectedDate = selectedDate ?? Date() if let popoverController = calendarVC.popoverPresentationController { popoverController.delegate = self popoverController.sourceView = containerView popoverController.sourceRect = containerView.bounds - popoverController.permittedArrowDirections = .any + popoverController.permittedArrowDirections = .up } if let viewController = UIApplication.topViewController() { viewController.present(calendarVC, animated: true, completion: nil) @@ -179,44 +184,160 @@ open class DatePicker: EntryFieldBase, DatePickerPopoverViewControllerDelegate, } } -protocol DatePickerPopoverViewControllerDelegate: NSObject { - func didSelectDate(_ controller: DatePickerPopoverViewController, date: Date) -} - -class DatePickerPopoverViewController: UIViewController { - - private let picker = UIDatePicker() - weak var delegate: DatePickerPopoverViewControllerDelegate? - - init(calendar: Calendar, delegate: DatePickerPopoverViewControllerDelegate?) { - self.delegate = delegate - super.init(nibName: nil, bundle: nil) - picker.datePickerMode = .date - picker.preferredDatePickerStyle = .inline - picker.addTarget(self, action: #selector(dateChanged(_:)), for: .valueChanged) - } - - var selectedDate: Date = Date() { - didSet { - picker.date = selectedDate +extension DatePicker { + public struct CalendarModel { + public let surface: Surface + + /// If set to true, the calendar will not have a border. + public let hideContainerBorder: Bool + + /// If set to true, the calendar will not have current date indication. + public let hideCurrentDateIndicator: Bool + + /// Enable specific days. Pass an array of string value in date format e.g. ['07/21/2024', '07/24/2024', 07/28/2024']. + /// All other dates will be inactive. + public let activeDates: [Date] + + /// Disable specific days. Pass an array of string value in date format e.g. ['07/21/2024', '07/24/2024', 07/28/2024']. + /// All other dates will be active. + public let inactiveDates: [Date] + + /// If provided, the calendar will allow a selection to be made from this date forward. Defaults to today. + public let minDate: Date + + /// If provided, the calendar will allow a selection to be made up to this date. + public let maxDate: Date + + /// If provided, this is the date that will show as selected by the Calendar. + /// If no value is provided, the current date will be used. If null is provided, no date will be selected. + public let selectedDate: Date + + /// Array of ``CalendarIndicatorModel`` you are wanting to show on legend. + public let indicators: [CalendarBase.CalendarIndicatorModel] + + public init(surface: Surface = .light, + hideContainerBorder: Bool = false, + hideCurrentDateIndicator: Bool = false, + selectedDate: Date = Date(), + activeDates: [Date] = [], + inactiveDates: [Date] = [], + minDate: Date = Date().startOfMonth, + maxDate: Date = Date().endOfMonth, + indicators: [CalendarBase.CalendarIndicatorModel] = []) { + self.surface = surface + self.hideContainerBorder = hideContainerBorder + self.hideCurrentDateIndicator = hideCurrentDateIndicator + self.selectedDate = selectedDate + self.activeDates = activeDates + self.inactiveDates = inactiveDates + self.minDate = minDate + self.maxDate = maxDate + self.indicators = indicators } } +} - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } +protocol DatePickerPopoverViewControllerDelegate: NSObject { + func didSelectDate(_ controller: DatePicker.DatePickerPopoverViewController, date: Date) +} - override func loadView() { - view = picker - view.backgroundColor = .white - } - - override func viewDidLoad() { - super.viewDidLoad() - preferredContentSize = CGSize(width: 300, height: 400) // Adjust as needed - } - - @objc private func dateChanged(_ sender: UIDatePicker) { - delegate?.didSelectDate(self, date: sender.date) +//class DatePickerPopoverViewController: UIViewController { +// +// private let picker = UIDatePicker() +// weak var delegate: DatePickerPopoverViewControllerDelegate? +// +// init(calendar: Calendar, delegate: DatePickerPopoverViewControllerDelegate?) { +// self.delegate = delegate +// super.init(nibName: nil, bundle: nil) +// picker.datePickerMode = .date +// picker.preferredDatePickerStyle = .inline +// picker.addTarget(self, action: #selector(dateChanged(_:)), for: .valueChanged) +// } +// +// var selectedDate: Date = Date() { +// didSet { +// picker.date = selectedDate +// } +// } +// +// required init?(coder: NSCoder) { +// fatalError("init(coder:) has not been implemented") +// } +// +// override func viewDidLoad() { +// super.viewDidLoad() +// let v = UIView().with { +// $0.translatesAutoresizingMaskIntoConstraints = false +// $0.backgroundColor = .white +// $0.width(constant: 250) +// $0.height(constant: 350) +// } +// view.addSubview(v) +// v.pinTop(25).pinLeading(15).pinTrailing(15).pinBottom(15) +// +// view.backgroundColor = .blue +// +// preferredContentSize = CGSize(width: 300, height: 400) // Adjust as needed +// } +// +// @objc private func dateChanged(_ sender: UIDatePicker) { +// delegate?.didSelectDate(self, date: sender.date) +// } +//} +extension DatePicker { + class DatePickerPopoverViewController: UIViewController { + private var padding: CGFloat = 15 + private var topPadding: CGFloat { 10 + padding } + private var calendarModel: CalendarModel + private let picker = CalendarBase() + weak var delegate: DatePickerPopoverViewControllerDelegate? + + init(_ calendarModel: CalendarModel, delegate: DatePickerPopoverViewControllerDelegate?) { + self.delegate = delegate + self.calendarModel = calendarModel + super.init(nibName: nil, bundle: nil) + self.picker.onChangeSelectedDate = { [weak self] date in + guard let self else { return } + self.delegate?.didSelectDate(self, date: date) + } + } + + var selectedDate: Date = Date() { + didSet { + picker.selectedDate = selectedDate + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + view.addSubview(picker) + picker.surface = calendarModel.surface + picker.hideContainerBorder = calendarModel.hideContainerBorder + picker.hideCurrentDateIndicator = calendarModel.hideCurrentDateIndicator + picker.activeDates = calendarModel.activeDates + picker.inactiveDates = calendarModel.inactiveDates + picker.selectedDate = calendarModel.selectedDate + picker.indicators = calendarModel.indicators + picker.minDate = calendarModel.minDate + picker.maxDate = calendarModel.maxDate + picker.pinToSuperView(.init(top: topPadding, left: padding, bottom: padding, right: padding)) + view.backgroundColor = picker.backgroundColor + } + + override var preferredContentSize: CGSize { + get { + var size = picker.containerSize + size.height += 40 + size.width += 30 + return size + } + set { + super.preferredContentSize = newValue + } + } } } From 561b47610bebedebaf85fb6ce79c910b24d021e2 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 14 May 2024 13:24:05 -0500 Subject: [PATCH 40/46] updated cornerRadius Signed-off-by: Matt Bruce --- VDS/Components/TileContainer/TileContainer.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VDS/Components/TileContainer/TileContainer.swift b/VDS/Components/TileContainer/TileContainer.swift index 08b87a7f..ffd16496 100644 --- a/VDS/Components/TileContainer/TileContainer.swift +++ b/VDS/Components/TileContainer/TileContainer.swift @@ -187,7 +187,7 @@ open class TileContainerBase: Control where Padding //-------------------------------------------------- // MARK: - Configuration //-------------------------------------------------- - private let cornerRadius = VDSFormControls.borderRadius * 2 + private let cornerRadius = VDSLayout.shapeCornerradiusTiles internal var backgroundColorConfiguration = BackgroundColorConfiguration() private let dropShadowConfiguration = DropShadowConfiguration().with { $0.shadowColorConfiguration = SurfaceColorConfiguration().with { From 49ec99e9bed479b75664a5b8e763adb653809363 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 14 May 2024 16:20:00 -0500 Subject: [PATCH 41/46] refactored code Signed-off-by: Matt Bruce --- VDS/Components/Calendar/Calendar.swift | 151 ++++++++++-------- .../Calendar/CalendarDateViewCell.swift | 89 ++++++----- .../Calendar/CalendarFooterReusableView.swift | 7 +- .../Calendar/CalendarHeaderReusableView.swift | 4 +- VDS/Components/Calendar/Date+Extension.swift | 35 ++-- 5 files changed, 159 insertions(+), 127 deletions(-) diff --git a/VDS/Components/Calendar/Calendar.swift b/VDS/Components/Calendar/Calendar.swift index 9ec8e6fc..18465c47 100644 --- a/VDS/Components/Calendar/Calendar.swift +++ b/VDS/Components/Calendar/Calendar.swift @@ -69,7 +69,8 @@ open class CalendarBase: View { // MARK: - Private Properties //-------------------------------------------------- internal var containerSize: CGSize { CGSize(width: 328, height: 376) } - + internal var calendar = Calendar.current + private let cellItemSize = CGSize(width: 40, height: 40) private let headerHeight = 88.0 private let footerHeight = 40.0 @@ -96,10 +97,14 @@ open class CalendarBase: View { collectionView.showsHorizontalScrollIndicator = false collectionView.showsVerticalScrollIndicator = false collectionView.backgroundColor = .clear - collectionView.register(CalendarDateViewCell.self, forCellWithReuseIdentifier: CalendarDateViewCell.identifier) + + collectionView.register(CalendarDateViewCell.self, + forCellWithReuseIdentifier: CalendarDateViewCell.identifier) + collectionView.register(CalendarHeaderReusableView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: CalendarHeaderReusableView.identifier) + collectionView.register(CalendarFooterReusableView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter, withReuseIdentifier: CalendarFooterReusableView.identifier) @@ -138,6 +143,7 @@ open class CalendarBase: View { containerView.addSubview(collectionView) let calendarHeight = containerSize.height - (2 * VDSLayout.space4X) let spacing = (containerSize.width - calendarWidth) / 2 + collectionView .pinTop(VDSLayout.space4X) .pinBottom(VDSLayout.space4X) @@ -145,7 +151,8 @@ open class CalendarBase: View { .pinTrailing(spacing) .width(calendarWidth) .heightGreaterThanEqualTo(calendarHeight) - collectionView.centerXAnchor.constraint(equalTo: containerView.centerXAnchor).activate() + + collectionView.pinCenterX(anchor: containerView.centerXAnchor) } open override func updateView() { @@ -155,7 +162,7 @@ open class CalendarBase: View { // Check if current date falls between min & max dates. let fallsBetween = displayDate.isBetweeen(date: minDate, andDate: maxDate) displayDate = fallsBetween ? displayDate : minDate - self.fetchDates(with: displayDate) + fetchDates(with: displayDate) } layer.backgroundColor = backgroundColorConfiguration.getColor(self).cgColor @@ -169,11 +176,7 @@ open class CalendarBase: View { layer.cornerRadius = VDSFormControls.borderRadius } } - - override open func layoutSubviews() { - super.layoutSubviews() - } - + /// Resets to default settings. open override func reset() { super.reset() @@ -188,41 +191,31 @@ open class CalendarBase: View { //-------------------------------------------------- // MARK: - Private Methods //-------------------------------------------------- - func fetchDates(with aDate:Date) { + func fetchDates(with aDate: Date) { heightConstraint?.isActive = false containerHeightConstraint?.isActive = false days.removeAll() - self.dates = aDate.calendarDisplayDays + dates = aDate.calendarDisplayDays + for date in dates { // code to be executed if date.monthInt != aDate.monthInt { days.append("") } else { - days.append(getDay(with: date)) + days.append(date.getDay()) } } - self.collectionView.reloadData() - var height = self.collectionView.collectionViewLayout.collectionViewContentSize.height - height = (height > 0) ? height : containerSize.height + + collectionView.reloadData() + + var height = collectionView.collectionViewLayout.collectionViewContentSize.height + height = height > 0 ? height : containerSize.height heightConstraint = collectionView.heightAnchor.constraint(equalToConstant: height) - containerHeightConstraint = containerView.heightAnchor.constraint(equalToConstant: (height + (2 * VDSLayout.space4X))) + containerHeightConstraint = containerView.heightAnchor.constraint(equalToConstant: height + (2 * VDSLayout.space4X)) heightConstraint?.isActive = true containerHeightConstraint?.isActive = true - self.layoutIfNeeded() - } - - func getDay(with date:Date) -> String { - if #available(iOS 15.0, *) { - return date.formatted(.dateTime.day()) - } else { - // Fallback on earlier versions - let dateFormatter: DateFormatter = DateFormatter() - dateFormatter.dateFormat = "d" - let day: String = dateFormatter.string(from: date) - return day - } - } - + layoutIfNeeded() + } } extension CalendarBase: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { @@ -235,21 +228,41 @@ extension CalendarBase: UICollectionViewDelegate, UICollectionViewDataSource, UI public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CalendarDateViewCell.identifier, for: indexPath) as? CalendarDateViewCell else { return UICollectionViewCell() } + var indicatorCount = 0 - if self.indicators.count > 0 { - for x in (0...(self.indicators.count-1)) { - if (self.days[indexPath.row] == self.getDay(with: self.indicators[x].date)) { + + if indicators.count > 0 { + for x in 0...indicators.count - 1 { + if days[indexPath.row] == indicators[x].date.getDay() { indicatorCount += 1 } } } - cell.update(with: surface, indicators: indicators, text: days[indexPath.row], indicatorCount: indicatorCount, selectedDate: selectedDate, displayDate: displayDate, hideDate: hideCurrentDateIndicator, minDate: minDate, maxDate: maxDate, activeDates: activeDates, inactiveDates: inactiveDates) - if (self.days[indexPath.row] == self.getDay(with: selectedDate)) { selectedIndexPath = indexPath } + + cell.update(with: surface, + indicators: indicators, + text: days[indexPath.row], + indicatorCount: indicatorCount, + selectedDate: selectedDate, + displayDate: displayDate, + hideDate: hideCurrentDateIndicator, + minDate: minDate, + maxDate: maxDate, + activeDates: activeDates, + inactiveDates: inactiveDates) + + if days[indexPath.row] == selectedDate.getDay() { + selectedIndexPath = indexPath + } + return cell } public func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { - if kind == UICollectionView.elementKindSectionHeader { + + switch kind { + case UICollectionView.elementKindSectionHeader: + // Header guard let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: CalendarHeaderReusableView.identifier, for: indexPath) as? CalendarHeaderReusableView else { return UICollectionReusableView() @@ -258,42 +271,51 @@ extension CalendarBase: UICollectionViewDelegate, UICollectionViewDataSource, UI var prevEnabled = false // check the interval between min date, max date.. set enable/disable flag for next / previous buttons. - if ((displayDate.monthInt < maxDate.monthInt) && (displayDate.yearInt == maxDate.yearInt)) || (displayDate.yearInt < maxDate.yearInt) { + if displayDate.monthInt < maxDate.monthInt && displayDate.yearInt == maxDate.yearInt + || displayDate.yearInt < maxDate.yearInt { nextEnabled = true } - if ((minDate.monthInt < displayDate.monthInt) && (minDate.yearInt == displayDate.yearInt)) || (minDate.yearInt < displayDate.yearInt) { + if minDate.monthInt < displayDate.monthInt && minDate.yearInt == displayDate.yearInt + || minDate.yearInt < displayDate.yearInt { prevEnabled = true } header.nextClicked = { [weak self] in guard let self = self else { return } - let aDate = Calendar.current.date(byAdding: .month, value: 1, to:self.displayDate)! - if ((aDate.monthInt <= maxDate.monthInt) && (aDate.yearInt == maxDate.yearInt)) || (aDate.yearInt < maxDate.yearInt) { - displayDate = aDate - self.fetchDates(with: displayDate) + let date = calendar.date(byAdding: .month, value: 1, to:displayDate)! + if date.monthInt <= maxDate.monthInt && date.yearInt == maxDate.yearInt + || date.yearInt < maxDate.yearInt { + displayDate = date + fetchDates(with: displayDate) } } + header.previousClicked = { [weak self] in guard let self = self else { return } - let aDate = Calendar.current.date(byAdding: .month, value: -1, to:self.displayDate)! - if ((minDate.monthInt <= aDate.monthInt) && (minDate.yearInt == aDate.yearInt)) || (minDate.yearInt < aDate.yearInt) { - displayDate = aDate - self.fetchDates(with: displayDate) + let date = calendar.date(byAdding: .month, value: -1, to:displayDate)! + if minDate.monthInt <= date.monthInt && minDate.yearInt == date.yearInt || (minDate.yearInt < date.yearInt) { + displayDate = date + fetchDates(with: displayDate) } } - header.update(with: surface, aDate: displayDate, nextEnabled: nextEnabled, previousEnabled: prevEnabled) + header.update(with: surface, date: displayDate, nextEnabled: nextEnabled, previousEnabled: prevEnabled) + return header - } else { - // Footer - if kind == UICollectionView.elementKindSectionFooter { - guard let footer = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: CalendarFooterReusableView.identifier, for: indexPath) as? CalendarFooterReusableView else { - return UICollectionReusableView() - } - footer.update(with: surface, indicators: indicators) - return footer + + case UICollectionView.elementKindSectionFooter: + + guard let footer = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: CalendarFooterReusableView.identifier, for: indexPath) as? CalendarFooterReusableView else { + return UICollectionReusableView() } + footer.update(with: surface, indicators: indicators) + + return footer + + default: + + return UICollectionReusableView() + } - return UICollectionReusableView() } public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { @@ -301,18 +323,21 @@ extension CalendarBase: UICollectionViewDelegate, UICollectionViewDataSource, UI if let cell = collectionView.cellForItem(at: indexPath) as? CalendarDateViewCell { let isEnabled: Bool = cell.isDateEnabled() if isEnabled { - // Callback to pass selected date if it is enabled only. - let selectedItem = Calendar.current.date(byAdding: .day, value: 1, to: self.dates[indexPath.row])! - onChangeSelectedDate?(selectedItem) - selectedDate = self.dates[indexPath.row] + // Callback to pass selected date if it is enabled only. + selectedDate = dates[indexPath.row] + onChangeSelectedDate?(selectedDate) displayDate = selectedDate + var reloadIndexPaths = [indexPath] // If an cell is already selected, then it needs to be deselected. // Add its index path to the array of index paths to be reloaded. - if let deselectIndexPath = selectedIndexPath { reloadIndexPaths.append(deselectIndexPath) } - self.collectionView.reloadItems(at: reloadIndexPaths) + if let deselectIndexPath = selectedIndexPath { + reloadIndexPaths.append(deselectIndexPath) + } + + collectionView.reloadItems(at: reloadIndexPaths) } } } diff --git a/VDS/Components/Calendar/CalendarDateViewCell.swift b/VDS/Components/Calendar/CalendarDateViewCell.swift index 26886d0a..b9f7359b 100644 --- a/VDS/Components/Calendar/CalendarDateViewCell.swift +++ b/VDS/Components/Calendar/CalendarDateViewCell.swift @@ -80,7 +80,7 @@ final class CalendarDateViewCell: UICollectionViewCell { .pinTrailingLessThanOrEqualTo() .height(containerSize.height) .width(containerSize.width) - containerView.centerXAnchor.constraint(equalTo: centerXAnchor).activate() + .pinCenterX() // Number label containerView.addSubview(numberLabel) @@ -89,8 +89,12 @@ final class CalendarDateViewCell: UICollectionViewCell { // Indicators containerView.addSubview(stackView) let topPos = containerSize.height * 0.7 - stackView.pinTop(topPos).pinBottom().pinTopGreaterThanOrEqualTo().pinTrailingLessThanOrEqualTo() - stackView.centerXAnchor.constraint(equalTo: centerXAnchor).activate() + stackView + .pinTop(topPos) + .pinBottom() + .pinTopGreaterThanOrEqualTo() + .pinTrailingLessThanOrEqualTo() + .pinCenterX() } /// Updating UI based on selected date, modified indicators data along with surface. @@ -102,12 +106,12 @@ final class CalendarDateViewCell: UICollectionViewCell { numberLabel.text = text // enable/disable cells based on min date, max date and active/inactive dates. - self.updateLabel(with:surface, displayDate: displayDate, minDate: minDate, maxDate: maxDate, activeDates: activeDates, inactiveDates: inactiveDates) + updateLabel(with:surface, displayDate: displayDate, minDate: minDate, maxDate: maxDate, activeDates: activeDates, inactiveDates: inactiveDates) // handling inactive dates. if inactiveDates.count > 0 { - for x in (0...(inactiveDates.count-1)) { - if (inactiveDates[x].monthInt == displayDate.monthInt) && (inactiveDates[x].yearInt == displayDate.yearInt) { + for x in 0...inactiveDates.count-1 { + if inactiveDates[x].monthInt == displayDate.monthInt && inactiveDates[x].yearInt == displayDate.yearInt { if let day:Int = Int(numberLabel.text), day == inactiveDates[x].dayInt { disableLabel(with: surface) } @@ -116,10 +120,15 @@ final class CalendarDateViewCell: UICollectionViewCell { } // update text color, bg color, corner radius. - if (numberLabel.text == self.getDay(with: selectedDate)) && (selectedDate.monthInt == displayDate.monthInt) && (selectedDate.yearInt == displayDate.yearInt) && numberLabel.isEnabled { + if numberLabel.text == selectedDate.getDay() + && selectedDate.monthInt == displayDate.monthInt + && selectedDate.yearInt == displayDate.yearInt + && numberLabel.isEnabled { + numberLabel.textColor = selectedTextColorConfiguration.getColor(surface) layer.backgroundColor = selectedBackgroundColor.getColor(surface).cgColor layer.cornerRadius = VDSFormControls.borderRadius + } else { numberLabel.textColor = unselectedTextColorConfiguration.getColor(surface) layer.backgroundColor = nil @@ -128,19 +137,18 @@ final class CalendarDateViewCell: UICollectionViewCell { // add indicators. if indicatorCount > 0 { - for x in (0...(indicators.count-1)) { - // irrespective of month and year, if it needs to show indicators on every month - comment below first if condition, else uncomment it. - // if (indicators[x].date.monthInt == displayDate.monthInt) && (indicators[x].date.yearInt == displayDate.yearInt) { - if (self.numberLabel.text == self.getDay(with: indicators[x].date)) { - let color = (numberLabel.text == self.getDay(with: selectedDate)) ? selectedCellIndicatorColorConfiguration.getColor(surface) : unselectedCellIndicatorColorConfiguration.getColor(surface) - addIndicator(with: color, surface: surface, clearFullCircle: (x == 1), drawSemiCircle: (x == 2)) - } - // } + for x in 0...indicators.count-1 { + if numberLabel.text == indicators[x].date.getDay() { + let color = numberLabel.text == selectedDate.getDay() ? selectedCellIndicatorColorConfiguration.getColor(surface) : unselectedCellIndicatorColorConfiguration.getColor(surface) + addIndicator(with: color, surface: surface, clearFullCircle: x == 1, drawSemiCircle: x == 2) + } } } // update text style for current date. - if (numberLabel.text == self.getDay(with: currentDate)) && (currentDate.monthInt == displayDate.monthInt) && (currentDate.yearInt == displayDate.yearInt) { + if numberLabel.text == currentDate.getDay() + && currentDate.monthInt == displayDate.monthInt + && currentDate.yearInt == displayDate.yearInt { numberLabel.textStyle = hideDate ? .bodySmall : .boldBodySmall } else { numberLabel.textStyle = .bodySmall @@ -159,8 +167,8 @@ final class CalendarDateViewCell: UICollectionViewCell { } func showActiveDates(with displayDate: Date, activeDates: [Date], inactiveDates: [Date]) { - for x in (0...(activeDates.count-1)) { - if (activeDates[x].monthInt == displayDate.monthInt) && (activeDates[x].yearInt == displayDate.yearInt ) { + for x in 0...activeDates.count-1 { + if activeDates[x].monthInt == displayDate.monthInt && activeDates[x].yearInt == displayDate.yearInt { if let day:Int = Int(numberLabel.text), day == activeDates[x].dayInt { numberLabel.isEnabled = true } @@ -189,7 +197,7 @@ final class CalendarDateViewCell: UICollectionViewCell { func minDateValidation(with surface:Surface, minDate: Date, displayDate: Date, activeDates: [Date], inactiveDates: [Date]) { // validate days to enable/disable with min date only. - if let day:Int = Int(numberLabel.text), day < minDate.dayInt { + if let day = Int(numberLabel.text), day < minDate.dayInt { disableLabel(with: surface) } else { numberLabel.isEnabled = false @@ -199,7 +207,7 @@ final class CalendarDateViewCell: UICollectionViewCell { func maxDateValidation(with surface:Surface, maxDate: Date, displayDate: Date, activeDates: [Date], inactiveDates: [Date]) { // validate days to enable/disable with max date only. - if let day:Int = Int(numberLabel.text), day > maxDate.dayInt { + if let day = Int(numberLabel.text), day > maxDate.dayInt { disableLabel(with: surface) } else { numberLabel.isEnabled = false @@ -209,7 +217,7 @@ final class CalendarDateViewCell: UICollectionViewCell { func minAndMaxDateValidation(with surface:Surface, minDate: Date, maxDate: Date, displayDate: Date, activeDates: [Date], inactiveDates: [Date]) { // validate days to enable/disable with min and max date. - if let day:Int = Int(numberLabel.text), day < minDate.dayInt || day > maxDate.dayInt { + if let day = Int(numberLabel.text), day < minDate.dayInt || day > maxDate.dayInt { disableLabel(with: surface) } else { numberLabel.isEnabled = false @@ -220,33 +228,33 @@ final class CalendarDateViewCell: UICollectionViewCell { // enable/disable cells based on min date, max date and active/inactive dates. func updateLabel(with surface: Surface, displayDate: Date, minDate: Date, maxDate: Date, activeDates: [Date], inactiveDates: [Date]) { - if (minDate.yearInt == displayDate.yearInt) && !(maxDate.yearInt == displayDate.yearInt) { + if minDate.yearInt == displayDate.yearInt && !(maxDate.yearInt == displayDate.yearInt) { // min year and max year are different, and matched to min year. - if (minDate.monthInt == displayDate.monthInt) { + if minDate.monthInt == displayDate.monthInt { // min year and max year are different, and matched to min year and min month. minDateValidation(with: surface, minDate: minDate, displayDate: displayDate, activeDates: activeDates, inactiveDates: inactiveDates) } else { // handing active dates - enable all days if no active dates. enableAllDaysAndCheckActiveDates(with: surface, displayDate: displayDate, activeDates: activeDates, inactiveDates: inactiveDates) } - } else if (maxDate.yearInt == displayDate.yearInt) && !((minDate.yearInt == displayDate.yearInt)) { + } else if maxDate.yearInt == displayDate.yearInt && !(minDate.yearInt == displayDate.yearInt) { // min year and max year are different, and matched to max year. - if (maxDate.monthInt == displayDate.monthInt) { + if maxDate.monthInt == displayDate.monthInt { // min year and max year are different, and matched to max year and max month. maxDateValidation(with: surface, maxDate: maxDate, displayDate: displayDate, activeDates: activeDates, inactiveDates: inactiveDates) } else { // handing active dates - enable all days if no active dates. enableAllDaysAndCheckActiveDates(with: surface, displayDate: displayDate, activeDates: activeDates, inactiveDates: inactiveDates) } - } else if (minDate.yearInt == displayDate.yearInt) && (maxDate.yearInt == displayDate.yearInt) { + } else if minDate.yearInt == displayDate.yearInt && maxDate.yearInt == displayDate.yearInt { // min year and max year same - if (minDate.monthInt == displayDate.monthInt) && (maxDate.monthInt == displayDate.monthInt) { + if minDate.monthInt == displayDate.monthInt && maxDate.monthInt == displayDate.monthInt { // min year and max year same, when choose dates in same month. minAndMaxDateValidation(with: surface, minDate: minDate, maxDate: maxDate, displayDate: displayDate, activeDates: activeDates, inactiveDates: inactiveDates) - } else if (minDate.monthInt == displayDate.monthInt) || (maxDate.monthInt == displayDate.monthInt) { + } else if minDate.monthInt == displayDate.monthInt || maxDate.monthInt == displayDate.monthInt { // min year and max year same, and choose dates in different months. - if (minDate.monthInt == displayDate.monthInt) { + if minDate.monthInt == displayDate.monthInt { // min year and max year same, and matched to min month. minDateValidation(with: surface, minDate: minDate, displayDate: displayDate, activeDates: activeDates, inactiveDates: inactiveDates) } else if (maxDate.monthInt == displayDate.monthInt) { @@ -272,14 +280,21 @@ final class CalendarDateViewCell: UICollectionViewCell { $0.backgroundColor = .clear $0.layer.borderWidth = 1.0 } - indicatorView.pinLeading().pinTrailing().width(8).height(8).pinCenterY() + + indicatorView + .pinLeading() + .pinTrailing() + .width(8) + .height(8) + .pinCenterY() + stackView.addArrangedSubview(indicatorView) // update indicator indicatorView.backgroundColor = drawSemiCircle ? .clear : (clearFullCircle ? .clear : color) indicatorView.layer.borderColor = color.cgColor - self.layoutIfNeeded() + layoutIfNeeded() indicatorView.layer.cornerRadius = indicatorView.frame.size.height / 2.0 @@ -296,16 +311,4 @@ final class CalendarDateViewCell: UICollectionViewCell { guard indicatorView.layer.sublayers?.contains(shapeLayer) ?? true else { return } indicatorView.layer.addSublayer(shapeLayer) } - - func getDay(with date: Date) -> String { - if #available(iOS 15.0, *) { - return date.formatted(.dateTime.day()) - } else { - // Fallback on earlier versions - let dateFormatter: DateFormatter = DateFormatter() - dateFormatter.dateFormat = "d" - let day: String = dateFormatter.string(from: date) - return day - } - } } diff --git a/VDS/Components/Calendar/CalendarFooterReusableView.swift b/VDS/Components/Calendar/CalendarFooterReusableView.swift index 3e85a6ec..f8432bda 100644 --- a/VDS/Components/Calendar/CalendarFooterReusableView.swift +++ b/VDS/Components/Calendar/CalendarFooterReusableView.swift @@ -65,7 +65,6 @@ class CalendarFooterReusableView: UICollectionReusableView { addSubview(containerView) containerView -// .pinTop(VDSLayout.space6X) .pinTopLessThanOrEqualTo(topAnchor, VDSLayout.space6X, .defaultLow) .pinBottom() .pinLeadingGreaterThanOrEqualTo(leadingAnchor, VDSLayout.space3X, .defaultHigh) @@ -99,7 +98,11 @@ extension CalendarFooterReusableView: UICollectionViewDelegate, UICollectionView let cell = collectionView.dequeueReusableCell(withReuseIdentifier: LegendCollectionViewCell.identifier, for: indexPath) as? LegendCollectionViewCell, indexPath.row <= items.count else { return UICollectionViewCell() } let text = items[indexPath.row].label - cell.updateTitle(text: text, color: VDSColor.elementsSecondaryOnlight, surface: self.surface, clearFullcircle: indexPath.row == 1, drawSemiCircle: indexPath.row == 2) + cell.updateTitle(text: text, + color: VDSColor.elementsSecondaryOnlight, + surface: self.surface, + clearFullcircle: indexPath.row == 1, + drawSemiCircle: indexPath.row == 2) return cell } } diff --git a/VDS/Components/Calendar/CalendarHeaderReusableView.swift b/VDS/Components/Calendar/CalendarHeaderReusableView.swift index b1d18810..d0f7f247 100644 --- a/VDS/Components/Calendar/CalendarHeaderReusableView.swift +++ b/VDS/Components/Calendar/CalendarHeaderReusableView.swift @@ -153,7 +153,7 @@ class CalendarHeaderReusableView: UICollectionReusableView { /// Updating UI based on next/previous clicks along with surface. /// Updating UI to enable/disable the next & previous buttons, updating header title. - func update(with surface: Surface, aDate: Date, nextEnabled: Bool, previousEnabled: Bool) { + func update(with surface: Surface, date: Date, nextEnabled: Bool, previousEnabled: Bool) { self.surface = surface headerTitle.surface = surface previousButton.surface = surface @@ -161,7 +161,7 @@ class CalendarHeaderReusableView: UICollectionReusableView { nextButton.isEnabled = nextEnabled previousButton.isEnabled = previousEnabled daysCollectionView.reloadData() - let labelText = aDate.getMonthName(date: aDate) + " \(aDate.yearInt)" + let labelText = date.getMonthName() + " \(date.yearInt)" headerTitle.text = labelText headerTitle.textColor = headerTitleTextColorConfiguration.getColor(surface) } diff --git a/VDS/Components/Calendar/Date+Extension.swift b/VDS/Components/Calendar/Date+Extension.swift index ca4f5a3b..25d8c9de 100644 --- a/VDS/Components/Calendar/Date+Extension.swift +++ b/VDS/Components/Calendar/Date+Extension.swift @@ -22,18 +22,6 @@ public extension Date { } } - /// Returns all month names - static var fullMonthNames: [String] { - let dateFormatter = DateFormatter() - dateFormatter.locale = Locale.current - - return (1...12).compactMap { month in - dateFormatter.setLocalizedDateFormatFromTemplate("MMMM") - let date = Calendar.current.date(from: DateComponents(year: 2000, month: month, day: 1)) - return date.map { dateFormatter.string(from: $0) } - } - } - var startOfMonth: Date { Calendar.current.dateInterval(of: .month, for: self)!.start } @@ -45,7 +33,7 @@ public extension Date { /// Get the number of days of the month var numberOfDaysInMonth: Int { - Calendar.current.component(.day, from: endOfMonth) + Calendar.current.range(of: .day, in: .month, for: self)!.count } var firstWeekDayBeforeStart: Date { @@ -98,9 +86,22 @@ public extension Date { } /// Returns the month name of the given date - func getMonthName(date: Date) -> String { - let names = Date.fullMonthNames - return names[date.monthInt - 1] + func getMonthName() -> String { + let dateFormatter = DateFormatter() + dateFormatter.locale = Locale.current + dateFormatter.setLocalizedDateFormatFromTemplate("MMMM") + return dateFormatter.string(from: self) + } + + func getDay() -> String { + if #available(iOS 15.0, *) { + return formatted(.dateTime.day()) + } else { + // Fallback on earlier versions + let dateFormatter: DateFormatter = DateFormatter() + dateFormatter.dateFormat = "d" + let day: String = dateFormatter.string(from: self) + return day + } } - } From 6d0e334f73bb1cd6665ec33b7ae86c929b09ab3f Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 14 May 2024 16:41:02 -0500 Subject: [PATCH 42/46] refactored out classes/structs into files Signed-off-by: Matt Bruce --- VDS.xcodeproj/project.pbxproj | 8 + VDS/Components/Calendar/Calendar.swift | 11 +- VDS/Components/DatePicker/DatePicker.swift | 169 +----------------- .../DatePicker/DatePickerCalendarModel.swift | 62 +++++++ .../DatePicker/DatePickerViewController.swift | 71 ++++++++ 5 files changed, 150 insertions(+), 171 deletions(-) create mode 100644 VDS/Components/DatePicker/DatePickerCalendarModel.swift create mode 100644 VDS/Components/DatePicker/DatePickerViewController.swift diff --git a/VDS.xcodeproj/project.pbxproj b/VDS.xcodeproj/project.pbxproj index 10329d45..b1a2ac68 100644 --- a/VDS.xcodeproj/project.pbxproj +++ b/VDS.xcodeproj/project.pbxproj @@ -159,6 +159,8 @@ EAC58C182BED0E2300BA39FA /* SecurityCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC58C172BED0E2300BA39FA /* SecurityCode.swift */; }; EAC58C232BF2824200BA39FA /* DatePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC58C222BF2824200BA39FA /* DatePicker.swift */; }; EAC58C252BF2A7FB00BA39FA /* DatePickerChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = EAC58C242BF2A7FB00BA39FA /* DatePickerChangeLog.txt */; }; + EAC58C272BF4116200BA39FA /* DatePickerCalendarModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC58C262BF4116200BA39FA /* DatePickerCalendarModel.swift */; }; + EAC58C292BF4118C00BA39FA /* DatePickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC58C282BF4118C00BA39FA /* DatePickerViewController.swift */; }; EAC71A1D2A2E155A00E47A9F /* Checkbox.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC71A1C2A2E155A00E47A9F /* Checkbox.swift */; }; EAC71A1F2A2E173D00E47A9F /* RadioButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC71A1E2A2E173D00E47A9F /* RadioButton.swift */; }; EAC846F3294B95CE00F685BA /* ButtonGroupCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC846F2294B95CE00F685BA /* ButtonGroupCollectionViewCell.swift */; }; @@ -373,6 +375,8 @@ EAC58C172BED0E2300BA39FA /* SecurityCode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecurityCode.swift; sourceTree = ""; }; EAC58C222BF2824200BA39FA /* DatePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatePicker.swift; sourceTree = ""; }; EAC58C242BF2A7FB00BA39FA /* DatePickerChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = DatePickerChangeLog.txt; sourceTree = ""; }; + EAC58C262BF4116200BA39FA /* DatePickerCalendarModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatePickerCalendarModel.swift; sourceTree = ""; }; + EAC58C282BF4118C00BA39FA /* DatePickerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatePickerViewController.swift; sourceTree = ""; }; EAC71A1C2A2E155A00E47A9F /* Checkbox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Checkbox.swift; sourceTree = ""; }; EAC71A1E2A2E173D00E47A9F /* RadioButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioButton.swift; sourceTree = ""; }; EAC846F2294B95CE00F685BA /* ButtonGroupCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonGroupCollectionViewCell.swift; sourceTree = ""; }; @@ -947,6 +951,8 @@ isa = PBXGroup; children = ( EAC58C222BF2824200BA39FA /* DatePicker.swift */, + EAC58C262BF4116200BA39FA /* DatePickerCalendarModel.swift */, + EAC58C282BF4118C00BA39FA /* DatePickerViewController.swift */, EAC58C242BF2A7FB00BA39FA /* DatePickerChangeLog.txt */, ); path = DatePicker; @@ -1281,6 +1287,7 @@ EA3361BD288B2C760071C351 /* TypeAlias.swift in Sources */, EAC58C0A2BED004E00BA39FA /* FieldType.swift in Sources */, EA471F3A2A95587500CE9E58 /* LayoutConstraintable.swift in Sources */, + EAC58C292BF4118C00BA39FA /* DatePickerViewController.swift in Sources */, EAB1D2CF28ABEF2B00DAE764 /* Typography+Base.swift in Sources */, EA0D1C3B2A6AD51B00E5C127 /* Typogprahy+Styles.swift in Sources */, EAF7F09A2899B17200B287F5 /* CATransaction.swift in Sources */, @@ -1327,6 +1334,7 @@ EA3361B6288B2A410071C351 /* Control.swift in Sources */, EAC58C122BED0DDD00BA39FA /* Text.swift in Sources */, 5F21D7BF28DCEB3D003E7CD6 /* Useable.swift in Sources */, + EAC58C272BF4116200BA39FA /* DatePickerCalendarModel.swift in Sources */, EAF7F0B7289C12A600B287F5 /* UITapGestureRecognizer.swift in Sources */, 1842B1DF2BECE28B0021AFCA /* CalendarDateViewCell.swift in Sources */, EA0D1C392A6AD4DF00E5C127 /* Typography+SpacingConfig.swift in Sources */, diff --git a/VDS/Components/Calendar/Calendar.swift b/VDS/Components/Calendar/Calendar.swift index 18465c47..1b1a797c 100644 --- a/VDS/Components/Calendar/Calendar.swift +++ b/VDS/Components/Calendar/Calendar.swift @@ -12,7 +12,7 @@ import Combine /// A calendar is a monthly view that lets customers select a single date. @objc(VDSCalendar) -open class CalendarBase: View { +open class CalendarBase: Control, Changeable { //-------------------------------------------------- // MARK: - Initializers @@ -32,6 +32,8 @@ open class CalendarBase: View { //-------------------------------------------------- // MARK: - Public Properties //-------------------------------------------------- + open var onChangeSubscriber: AnyCancellable? + /// If set to true, the calendar will not have a border. open var hideContainerBorder: Bool = false { didSet { setNeedsUpdate() } } @@ -61,10 +63,7 @@ open class CalendarBase: View { /// Array of ``CalendarIndicatorModel`` you are wanting to show on legend. open var indicators: [CalendarIndicatorModel] = [] { didSet { setNeedsUpdate() } } - - /// A callback when the date changes. Passes parameters (selectedDate). - public var onChangeSelectedDate: ((Date) -> Void)? - + //-------------------------------------------------- // MARK: - Private Properties //-------------------------------------------------- @@ -326,7 +325,7 @@ extension CalendarBase: UICollectionViewDelegate, UICollectionViewDataSource, UI // Callback to pass selected date if it is enabled only. selectedDate = dates[indexPath.row] - onChangeSelectedDate?(selectedDate) + sendActions(for: .valueChanged) displayDate = selectedDate var reloadIndexPaths = [indexPath] diff --git a/VDS/Components/DatePicker/DatePicker.swift b/VDS/Components/DatePicker/DatePicker.swift index 710de91a..ff9fbb06 100644 --- a/VDS/Components/DatePicker/DatePicker.swift +++ b/VDS/Components/DatePicker/DatePicker.swift @@ -5,7 +5,7 @@ import Combine /// A dropdown select is an expandable menu of predefined options that allows a customer to make a single selection. @objc(VDSDatePicker) -open class DatePicker: EntryFieldBase, DatePickerPopoverViewControllerDelegate, UIPopoverPresentationControllerDelegate { +open class DatePicker: EntryFieldBase, DatePickerViewControllerDelegate, UIPopoverPresentationControllerDelegate { //-------------------------------------------------- // MARK: - Initializers //-------------------------------------------------- @@ -52,9 +52,7 @@ open class DatePicker: EntryFieldBase, DatePickerPopoverViewControllerDelegate, open var selectedDate: Date? { didSet { setNeedsUpdate() } } - open var calendarModel: CalendarModel = .init() { - didSet { setNeedsUpdate() } - } + open var calendarModel: CalendarModel = .init() { didSet { setNeedsUpdate() } } open override var value: String? { get { selectedDateLabel.text } @@ -158,8 +156,7 @@ open class DatePicker: EntryFieldBase, DatePickerPopoverViewControllerDelegate, } internal func togglePicker() { - //let calendarVC = DatePickerPopoverViewController(calendar: Calendar(identifier: .gregorian), delegate: self) - let calendarVC = DatePickerPopoverViewController(calendarModel, delegate: self) + let calendarVC = DatePickerViewController(calendarModel, delegate: self) calendarVC.modalPresentationStyle = .popover calendarVC.selectedDate = selectedDate ?? Date() if let popoverController = calendarVC.popoverPresentationController { @@ -173,7 +170,7 @@ open class DatePicker: EntryFieldBase, DatePickerPopoverViewControllerDelegate, } } - internal func didSelectDate(_ controller: DatePickerPopoverViewController, date: Date) { + internal func didSelectDate(_ controller: DatePickerViewController, date: Date) { selectedDate = date controller.dismiss(animated: true) sendActions(for: .valueChanged) @@ -183,161 +180,3 @@ open class DatePicker: EntryFieldBase, DatePickerPopoverViewControllerDelegate, return .none } } - -extension DatePicker { - public struct CalendarModel { - public let surface: Surface - - /// If set to true, the calendar will not have a border. - public let hideContainerBorder: Bool - - /// If set to true, the calendar will not have current date indication. - public let hideCurrentDateIndicator: Bool - - /// Enable specific days. Pass an array of string value in date format e.g. ['07/21/2024', '07/24/2024', 07/28/2024']. - /// All other dates will be inactive. - public let activeDates: [Date] - - /// Disable specific days. Pass an array of string value in date format e.g. ['07/21/2024', '07/24/2024', 07/28/2024']. - /// All other dates will be active. - public let inactiveDates: [Date] - - /// If provided, the calendar will allow a selection to be made from this date forward. Defaults to today. - public let minDate: Date - - /// If provided, the calendar will allow a selection to be made up to this date. - public let maxDate: Date - - /// If provided, this is the date that will show as selected by the Calendar. - /// If no value is provided, the current date will be used. If null is provided, no date will be selected. - public let selectedDate: Date - - /// Array of ``CalendarIndicatorModel`` you are wanting to show on legend. - public let indicators: [CalendarBase.CalendarIndicatorModel] - - public init(surface: Surface = .light, - hideContainerBorder: Bool = false, - hideCurrentDateIndicator: Bool = false, - selectedDate: Date = Date(), - activeDates: [Date] = [], - inactiveDates: [Date] = [], - minDate: Date = Date().startOfMonth, - maxDate: Date = Date().endOfMonth, - indicators: [CalendarBase.CalendarIndicatorModel] = []) { - self.surface = surface - self.hideContainerBorder = hideContainerBorder - self.hideCurrentDateIndicator = hideCurrentDateIndicator - self.selectedDate = selectedDate - self.activeDates = activeDates - self.inactiveDates = inactiveDates - self.minDate = minDate - self.maxDate = maxDate - self.indicators = indicators - } - } -} - -protocol DatePickerPopoverViewControllerDelegate: NSObject { - func didSelectDate(_ controller: DatePicker.DatePickerPopoverViewController, date: Date) -} - -//class DatePickerPopoverViewController: UIViewController { -// -// private let picker = UIDatePicker() -// weak var delegate: DatePickerPopoverViewControllerDelegate? -// -// init(calendar: Calendar, delegate: DatePickerPopoverViewControllerDelegate?) { -// self.delegate = delegate -// super.init(nibName: nil, bundle: nil) -// picker.datePickerMode = .date -// picker.preferredDatePickerStyle = .inline -// picker.addTarget(self, action: #selector(dateChanged(_:)), for: .valueChanged) -// } -// -// var selectedDate: Date = Date() { -// didSet { -// picker.date = selectedDate -// } -// } -// -// required init?(coder: NSCoder) { -// fatalError("init(coder:) has not been implemented") -// } -// -// override func viewDidLoad() { -// super.viewDidLoad() -// let v = UIView().with { -// $0.translatesAutoresizingMaskIntoConstraints = false -// $0.backgroundColor = .white -// $0.width(constant: 250) -// $0.height(constant: 350) -// } -// view.addSubview(v) -// v.pinTop(25).pinLeading(15).pinTrailing(15).pinBottom(15) -// -// view.backgroundColor = .blue -// -// preferredContentSize = CGSize(width: 300, height: 400) // Adjust as needed -// } -// -// @objc private func dateChanged(_ sender: UIDatePicker) { -// delegate?.didSelectDate(self, date: sender.date) -// } -//} -extension DatePicker { - class DatePickerPopoverViewController: UIViewController { - private var padding: CGFloat = 15 - private var topPadding: CGFloat { 10 + padding } - private var calendarModel: CalendarModel - private let picker = CalendarBase() - weak var delegate: DatePickerPopoverViewControllerDelegate? - - init(_ calendarModel: CalendarModel, delegate: DatePickerPopoverViewControllerDelegate?) { - self.delegate = delegate - self.calendarModel = calendarModel - super.init(nibName: nil, bundle: nil) - self.picker.onChangeSelectedDate = { [weak self] date in - guard let self else { return } - self.delegate?.didSelectDate(self, date: date) - } - } - - var selectedDate: Date = Date() { - didSet { - picker.selectedDate = selectedDate - } - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func viewDidLoad() { - super.viewDidLoad() - view.addSubview(picker) - picker.surface = calendarModel.surface - picker.hideContainerBorder = calendarModel.hideContainerBorder - picker.hideCurrentDateIndicator = calendarModel.hideCurrentDateIndicator - picker.activeDates = calendarModel.activeDates - picker.inactiveDates = calendarModel.inactiveDates - picker.selectedDate = calendarModel.selectedDate - picker.indicators = calendarModel.indicators - picker.minDate = calendarModel.minDate - picker.maxDate = calendarModel.maxDate - picker.pinToSuperView(.init(top: topPadding, left: padding, bottom: padding, right: padding)) - view.backgroundColor = picker.backgroundColor - } - - override var preferredContentSize: CGSize { - get { - var size = picker.containerSize - size.height += 40 - size.width += 30 - return size - } - set { - super.preferredContentSize = newValue - } - } - } -} diff --git a/VDS/Components/DatePicker/DatePickerCalendarModel.swift b/VDS/Components/DatePicker/DatePickerCalendarModel.swift new file mode 100644 index 00000000..b9b8c8f7 --- /dev/null +++ b/VDS/Components/DatePicker/DatePickerCalendarModel.swift @@ -0,0 +1,62 @@ +// +// DatePicker-CalendarModel.swift +// VDS +// +// Created by Matt Bruce on 5/14/24. +// + +import Foundation +import UIKit + +extension DatePicker { + public struct CalendarModel { + public let surface: Surface + + /// If set to true, the calendar will not have a border. + public let hideContainerBorder: Bool + + /// If set to true, the calendar will not have current date indication. + public let hideCurrentDateIndicator: Bool + + /// Enable specific days. Pass an array of string value in date format e.g. ['07/21/2024', '07/24/2024', 07/28/2024']. + /// All other dates will be inactive. + public let activeDates: [Date] + + /// Disable specific days. Pass an array of string value in date format e.g. ['07/21/2024', '07/24/2024', 07/28/2024']. + /// All other dates will be active. + public let inactiveDates: [Date] + + /// If provided, the calendar will allow a selection to be made from this date forward. Defaults to today. + public let minDate: Date + + /// If provided, the calendar will allow a selection to be made up to this date. + public let maxDate: Date + + /// If provided, this is the date that will show as selected by the Calendar. + /// If no value is provided, the current date will be used. If null is provided, no date will be selected. + public let selectedDate: Date + + /// Array of ``CalendarIndicatorModel`` you are wanting to show on legend. + public let indicators: [CalendarBase.CalendarIndicatorModel] + + public init(surface: Surface = .light, + hideContainerBorder: Bool = false, + hideCurrentDateIndicator: Bool = false, + selectedDate: Date = Date(), + activeDates: [Date] = [], + inactiveDates: [Date] = [], + minDate: Date = Date().startOfMonth, + maxDate: Date = Date().endOfMonth, + indicators: [CalendarBase.CalendarIndicatorModel] = []) { + self.surface = surface + self.hideContainerBorder = hideContainerBorder + self.hideCurrentDateIndicator = hideCurrentDateIndicator + self.selectedDate = selectedDate + self.activeDates = activeDates + self.inactiveDates = inactiveDates + self.minDate = minDate + self.maxDate = maxDate + self.indicators = indicators + } + } +} diff --git a/VDS/Components/DatePicker/DatePickerViewController.swift b/VDS/Components/DatePicker/DatePickerViewController.swift new file mode 100644 index 00000000..165dab75 --- /dev/null +++ b/VDS/Components/DatePicker/DatePickerViewController.swift @@ -0,0 +1,71 @@ +// +// DatePickerPopoverViewController.swift +// VDS +// +// Created by Matt Bruce on 5/14/24. +// + +import Foundation +import UIKit + +protocol DatePickerViewControllerDelegate: NSObject { + func didSelectDate(_ controller: DatePicker.DatePickerViewController, date: Date) +} + +extension DatePicker { + class DatePickerViewController: UIViewController { + private var padding: CGFloat = 15 + private var topPadding: CGFloat { 10 + padding } + private var calendarModel: CalendarModel + private let picker = CalendarBase() + weak var delegate: DatePickerViewControllerDelegate? + + init(_ calendarModel: CalendarModel, delegate: DatePickerViewControllerDelegate?) { + self.delegate = delegate + self.calendarModel = calendarModel + super.init(nibName: nil, bundle: nil) + self.picker.onChange = { [weak self] control in + guard let self else { return } + self.delegate?.didSelectDate(self, date: control.selectedDate) + } + } + + var selectedDate: Date = Date() { + didSet { + picker.selectedDate = selectedDate + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + view.addSubview(picker) + picker.surface = calendarModel.surface + picker.hideContainerBorder = calendarModel.hideContainerBorder + picker.hideCurrentDateIndicator = calendarModel.hideCurrentDateIndicator + picker.indicators = calendarModel.indicators + picker.activeDates = calendarModel.activeDates + picker.inactiveDates = calendarModel.inactiveDates + picker.selectedDate = calendarModel.selectedDate + picker.minDate = calendarModel.minDate + picker.maxDate = calendarModel.maxDate + picker.pinToSuperView(.init(top: topPadding, left: padding, bottom: padding, right: padding)) + view.backgroundColor = picker.backgroundColor + } + + override var preferredContentSize: CGSize { + get { + var size = picker.frame.size + size.height += 40 + size.width += 30 + return size + } + set { + super.preferredContentSize = newValue + } + } + } +} From 85adb2be69249029744c3913e304b2eaf5dfad8f Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 14 May 2024 16:43:49 -0500 Subject: [PATCH 43/46] fixed bug for seelcted date Signed-off-by: Matt Bruce --- VDS/Components/DatePicker/DatePickerCalendarModel.swift | 6 ------ VDS/Components/DatePicker/DatePickerViewController.swift | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/VDS/Components/DatePicker/DatePickerCalendarModel.swift b/VDS/Components/DatePicker/DatePickerCalendarModel.swift index b9b8c8f7..ccceb5c9 100644 --- a/VDS/Components/DatePicker/DatePickerCalendarModel.swift +++ b/VDS/Components/DatePicker/DatePickerCalendarModel.swift @@ -32,17 +32,12 @@ extension DatePicker { /// If provided, the calendar will allow a selection to be made up to this date. public let maxDate: Date - /// If provided, this is the date that will show as selected by the Calendar. - /// If no value is provided, the current date will be used. If null is provided, no date will be selected. - public let selectedDate: Date - /// Array of ``CalendarIndicatorModel`` you are wanting to show on legend. public let indicators: [CalendarBase.CalendarIndicatorModel] public init(surface: Surface = .light, hideContainerBorder: Bool = false, hideCurrentDateIndicator: Bool = false, - selectedDate: Date = Date(), activeDates: [Date] = [], inactiveDates: [Date] = [], minDate: Date = Date().startOfMonth, @@ -51,7 +46,6 @@ extension DatePicker { self.surface = surface self.hideContainerBorder = hideContainerBorder self.hideCurrentDateIndicator = hideCurrentDateIndicator - self.selectedDate = selectedDate self.activeDates = activeDates self.inactiveDates = inactiveDates self.minDate = minDate diff --git a/VDS/Components/DatePicker/DatePickerViewController.swift b/VDS/Components/DatePicker/DatePickerViewController.swift index 165dab75..f8a6e2f0 100644 --- a/VDS/Components/DatePicker/DatePickerViewController.swift +++ b/VDS/Components/DatePicker/DatePickerViewController.swift @@ -49,7 +49,7 @@ extension DatePicker { picker.indicators = calendarModel.indicators picker.activeDates = calendarModel.activeDates picker.inactiveDates = calendarModel.inactiveDates - picker.selectedDate = calendarModel.selectedDate + picker.selectedDate = selectedDate picker.minDate = calendarModel.minDate picker.maxDate = calendarModel.maxDate picker.pinToSuperView(.init(top: topPadding, left: padding, bottom: padding, right: padding)) From a031ea8077df1c47b93c3745e7ebf76168fc78e7 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 14 May 2024 17:25:50 -0500 Subject: [PATCH 44/46] CXTDT-557216 - BadgeIndicator - Accessibility Signed-off-by: Matt Bruce --- VDS/Components/BadgeIndicator/BadgeIndicator.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VDS/Components/BadgeIndicator/BadgeIndicator.swift b/VDS/Components/BadgeIndicator/BadgeIndicator.swift index aae927a8..463e5df8 100644 --- a/VDS/Components/BadgeIndicator/BadgeIndicator.swift +++ b/VDS/Components/BadgeIndicator/BadgeIndicator.swift @@ -350,7 +350,7 @@ open class BadgeIndicator: View { open override func updateAccessibility() { super.updateAccessibility() if let accessibilityText { - accessibilityLabel = accessibilityText + accessibilityLabel = kind == .numbered ? label.text + " " + accessibilityText : accessibilityText } else if kind == .numbered { accessibilityLabel = label.text } else { From 6826b0798d9baf74265a1c2c23debc0a19b5cd78 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 14 May 2024 17:42:22 -0500 Subject: [PATCH 45/46] updated release notes Signed-off-by: Matt Bruce --- VDS/SupportingFiles/ReleaseNotes.txt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/VDS/SupportingFiles/ReleaseNotes.txt b/VDS/SupportingFiles/ReleaseNotes.txt index 39447d3e..461efee9 100644 --- a/VDS/SupportingFiles/ReleaseNotes.txt +++ b/VDS/SupportingFiles/ReleaseNotes.txt @@ -1,7 +1,12 @@ 1.0.63 ---------------- - CXTDT-555860 - Dropdown Select - Form Elements layout option missing - +- ONEAPP-7963 - InputField - Finished Development +- ONEAPP-7958 - Calendar - Finished Development +- ONEAPP-7010 - DatePicker - Finished Development +- CXTDT-555860 - Dropdown Select - Form Elements layout option missing +- CXTDT-557216 - BadgeIndicator - Accessibility +- CXTDT-555854 - Dropdown Select - spacing issues 1.0.62 ---------------- From 70d94a1a48e0dde97ecd3998ee9d43820cab0e26 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 14 May 2024 17:45:04 -0500 Subject: [PATCH 46/46] updated version Signed-off-by: Matt Bruce --- VDS.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/VDS.xcodeproj/project.pbxproj b/VDS.xcodeproj/project.pbxproj index b1a2ac68..cca7c7f7 100644 --- a/VDS.xcodeproj/project.pbxproj +++ b/VDS.xcodeproj/project.pbxproj @@ -1509,7 +1509,7 @@ BUILD_LIBRARY_FOR_DISTRIBUTION = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 62; + CURRENT_PROJECT_VERSION = 63; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; @@ -1546,7 +1546,7 @@ BUILD_LIBRARY_FOR_DISTRIBUTION = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 62; + CURRENT_PROJECT_VERSION = 63; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1;