diff --git a/VDS.xcodeproj/project.pbxproj b/VDS.xcodeproj/project.pbxproj index 366fca71..79deb46b 100644 --- a/VDS.xcodeproj/project.pbxproj +++ b/VDS.xcodeproj/project.pbxproj @@ -16,6 +16,7 @@ 5F21D7BF28DCEB3D003E7CD6 /* Useable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F21D7BE28DCEB3D003E7CD6 /* Useable.swift */; }; 5FC35BE328D51405004EBEAC /* Button.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FC35BE228D51405004EBEAC /* Button.swift */; }; 7115BD3C2B84C0C200E0A610 /* TileContainerChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 7115BD3B2B84C0C200E0A610 /* TileContainerChangeLog.txt */; }; + 71B23C2D2B91FA690027F7D9 /* Pagination.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71B23C2C2B91FA690027F7D9 /* Pagination.swift */; }; 71BFA70A2B7F70E6000DCE33 /* Dropshadowable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71BFA7092B7F70E6000DCE33 /* Dropshadowable.swift */; }; 71C02B382B7BD98F00E93E66 /* NotificationChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 71C02B372B7BD98F00E93E66 /* NotificationChangeLog.txt */; }; EA0B18022A9E236900F2D0CD /* SelectorGroupBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA0B18012A9E236900F2D0CD /* SelectorGroupBase.swift */; }; @@ -184,6 +185,7 @@ 5F21D7BE28DCEB3D003E7CD6 /* Useable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Useable.swift; sourceTree = ""; }; 5FC35BE228D51405004EBEAC /* Button.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = ""; }; 7115BD3B2B84C0C200E0A610 /* TileContainerChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = TileContainerChangeLog.txt; sourceTree = ""; }; + 71B23C2C2B91FA690027F7D9 /* Pagination.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Pagination.swift; sourceTree = ""; }; 71BFA7092B7F70E6000DCE33 /* Dropshadowable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Dropshadowable.swift; sourceTree = ""; }; 71C02B372B7BD98F00E93E66 /* NotificationChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = NotificationChangeLog.txt; sourceTree = ""; }; EA0B18012A9E236900F2D0CD /* SelectorGroupBase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SelectorGroupBase.swift; sourceTree = ""; }; @@ -384,6 +386,14 @@ path = Button; sourceTree = ""; }; + 71B23C2B2B91FA510027F7D9 /* Pagination */ = { + isa = PBXGroup; + children = ( + 71B23C2C2B91FA690027F7D9 /* Pagination.swift */, + ); + path = Pagination; + sourceTree = ""; + }; EA0B17FF2A9E21CA00F2D0CD /* Selector */ = { isa = PBXGroup; children = ( @@ -487,6 +497,7 @@ EA33619D288B1E330071C351 /* Components */ = { isa = PBXGroup; children = ( + 71B23C2B2B91FA510027F7D9 /* Pagination */, EA4DB2FE28DCBC1900103EE3 /* Badge */, EAD062AE2A3B87210015965D /* BadgeIndicator */, EA0FC2BE2912D18200DF80B4 /* Buttons */, @@ -1051,6 +1062,7 @@ EA8E40932A82889500934ED3 /* TooltipDialog.swift in Sources */, 44604AD429CE186A00E62B51 /* NotificationButtonModel.swift in Sources */, EAD8D2C128BFDE8B006EB6A6 /* UIGestureRecognizer+Publisher.swift in Sources */, + 71B23C2D2B91FA690027F7D9 /* Pagination.swift in Sources */, EA0D1C372A681CCE00E5C127 /* ToggleView.swift in Sources */, EAF7F0B9289C139800B287F5 /* ColorConfiguration.swift in Sources */, EA3361BD288B2C760071C351 /* TypeAlias.swift in Sources */, diff --git a/VDS/Components/Pagination/Pagination.swift b/VDS/Components/Pagination/Pagination.swift new file mode 100644 index 00000000..4df0a034 --- /dev/null +++ b/VDS/Components/Pagination/Pagination.swift @@ -0,0 +1,228 @@ +// +// Pagination.swift +// VDS +// +// Created by Bandaru, Krishna Kishore on 01/03/24. +// + +import Foundation +import VDSColorTokens +import Combine + +@objc(VDSPagination) +open class Pagination: View { + + @Published var onPreviousTapped: PassthroughSubject = PassthroughSubject() + @Published var onNextTapped: PassthroughSubject = PassthroughSubject() + @Published var onPageWillChange: PassthroughSubject = PassthroughSubject() + @Published var onPageChanged: PassthroughSubject = PassthroughSubject() + + public var total: Int = 0 { + didSet { + setNeedsUpdate() + } + } + public var selectedPage: Int = 0 { didSet { setNeedsUpdate() } } + private var numberOfRows: Int = 0 { + didSet { + collectionView.collectionViewLayout.invalidateLayout() + setNeedsUpdate() + } + } + private let pageItemCellSize: CGSize = .init(width: 20, height: 16) + private let spacingBetweenCell: CGFloat = VDSLayout.Spacing.space1X.value + private let buttonTintColorConfiguration = SurfaceColorConfiguration(VDSColor.paletteBlack, VDSColor.paletteWhite) + private let buttonTextColorConfiguration = SurfaceColorConfiguration(VDSColor.paletteBlack, VDSColor.paletteWhite) + + private lazy var collectionView: UICollectionView = { + let layout = UICollectionViewFlowLayout() + layout.itemSize = pageItemCellSize + layout.scrollDirection = .horizontal + layout.minimumInteritemSpacing = spacingBetweenCell + layout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize + layout.minimumLineSpacing = spacingBetweenCell + layout.sectionInset = .zero + let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) + collectionView.isScrollEnabled = false + collectionView.translatesAutoresizingMaskIntoConstraints = false + collectionView.delegate = self + collectionView.dataSource = self + collectionView.showsHorizontalScrollIndicator = false + collectionView.showsVerticalScrollIndicator = false + collectionView.register(PaginationCellItem.self, forCellWithReuseIdentifier: PaginationCellItem.identifier) + collectionView.backgroundColor = .clear + return collectionView + }() + + //TODO: Need to check with textStyle with Matt as its getter only in ButtonBase + private lazy var previousButton: ButtonBase = { + let previousButton: ButtonBase + if #available(iOS 15.0, *) { + var configuration = ButtonBase.Configuration.plain() + configuration.imagePadding = VDSLayout.Spacing.space2X.value + configuration.attributedTitle = AttributedString("Previous", attributes: AttributeContainer([NSAttributedString.Key.font: TextStyle.boldBodySmall.font])) + configuration.titleAlignment = .leading + configuration.imagePlacement = .leading + configuration.contentInsets = .zero + previousButton = ButtonBase(configuration: configuration) + } else { + previousButton = ButtonBase() + previousButton.imageEdgeInsets = .init(top: 0, left: 0, bottom: 0, right: VDSLayout.Spacing.space2X.value) + previousButton.setTitle("Previous", for: .normal) + previousButton.titleLabel?.font = TextStyle.boldBodySmall.font + } + previousButton.contentHorizontalAlignment = .leading + previousButton.translatesAutoresizingMaskIntoConstraints = false + previousButton.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) + previousButton.setImage(BundleManager.shared.image(for: "pagination-arrow-left")?.withRenderingMode(.alwaysTemplate), for: .normal) + return previousButton + }() + + private let nextButton: ButtonBase = { + let nextButton: ButtonBase + if #available(iOS 15.0, *) { + var configuration = ButtonBase.Configuration.plain() + configuration.imagePadding = VDSLayout.Spacing.space2X.value + configuration.attributedTitle = AttributedString("Next", attributes: AttributeContainer([NSAttributedString.Key.font: TextStyle.boldBodySmall.font])) + configuration.imagePlacement = .trailing + configuration.titleAlignment = .trailing + configuration.contentInsets = .zero + nextButton = ButtonBase(configuration: configuration) + } else { + nextButton = ButtonBase() + nextButton.imageEdgeInsets = .init(top: 0, left: 0, bottom: 0, right: VDSLayout.Spacing.space2X.value) + nextButton.semanticContentAttribute = .forceRightToLeft + nextButton.titleLabel?.font = TextStyle.boldBodySmall.font + nextButton.setTitle("Next", for: .normal) + } + //nextButton.textStyle = .boldBodySmall + nextButton.translatesAutoresizingMaskIntoConstraints = false + nextButton.contentHorizontalAlignment = .trailing + nextButton.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) + nextButton.setImage(BundleManager.shared.image(for: "pagination-arrow-right")?.withRenderingMode(.alwaysTemplate), for: .normal) + return nextButton + }() + + private let containerView: View = View().with { + $0.translatesAutoresizingMaskIntoConstraints = false + } + + open override func initialSetup() { + super.initialSetup() + + addSubview(containerView) + containerView.pinToSuperView() + containerView.widthAnchor.constraint(greaterThanOrEqualToConstant: 288).activate() + containerView.heightAnchor.constraint(equalToConstant: 44).activate() + containerView.addSubview(previousButton) + containerView.addSubview(collectionView) + containerView.addSubview(nextButton) + + previousButton + .pinTop() + .pinBottom() + .pinLeading() + previousButton.trailingAnchor.constraint(greaterThanOrEqualTo: collectionView.leadingAnchor).activate() + collectionView.heightAnchor.constraint(equalToConstant: VDSLayout.Spacing.space4X.value).activate() + collectionView.centerYAnchor.constraint(equalTo: centerYAnchor).activate() + collectionView.centerXAnchor.constraint(equalTo: centerXAnchor).activate() + collectionView.trailingAnchor.constraint(greaterThanOrEqualTo: nextButton.leadingAnchor).activate() + collectionView.widthAnchor.constraint(equalToConstant: 92).activate() + nextButton + .pinTop() + .pinBottom() + .pinTrailing() + nextButton.onClick = onbuttonTapped + previousButton.onClick = onbuttonTapped + previousButton.isHidden = true + } + + open override func updateView() { + super.updateView() + + previousButton.tintColor = buttonTintColorConfiguration.getColor(surface) + nextButton.tintColor = buttonTintColorConfiguration.getColor(surface) + previousButton.setTitleColor(buttonTextColorConfiguration.getColor(surface), for: .normal) + nextButton.setTitleColor(buttonTextColorConfiguration.getColor(surface), for: .normal) + collectionView.reloadData() + } + + private func onbuttonTapped(_ sender: UIButton) { + let isNextAction = sender == nextButton + if isNextAction { + selectedPage += 1 + } else { + selectedPage -= 1 + } + updateSelection() + } + + private func updateSelection() { + collectionView.scrollToItem(at: IndexPath(row: max(selectedPage-1, 0), section: 0), at: .left, animated: false) + previousButton.isHidden = selectedPage == 0 + nextButton.isHidden = selectedPage == total - 1 + } +} + +extension Pagination: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { + + public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { total } + + public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: PaginationCellItem.identifier, for: indexPath) as? PaginationCellItem else { return UICollectionViewCell() } + cell.update(selectedPage, currentIndex: indexPath.row, surface: surface) + return cell + } + + public func collectionView(_ collectionView: UICollectionView, shouldHighlightItemAt indexPath: IndexPath) -> Bool { + onPageWillChange.send(selectedPage) + return true + } + + public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + selectedPage = indexPath.row + updateSelection() + onPageChanged.send(indexPath.row) + } +} + +internal final class PaginationCellItem: UICollectionViewCell { + + static let identifier: String = String(describing: PaginationCellItem.self) + private let textColorConfiguration = SurfaceColorConfiguration(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark) + + private var indexLabel: Label = Label().with { + $0.translatesAutoresizingMaskIntoConstraints = false + $0.textAlignment = .center + $0.numberOfLines = 1 + } + + override init(frame: CGRect) { + super.init(frame: frame) + setUp() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + setUp() + } + + private func setUp() { + let containerView = View() + containerView.translatesAutoresizingMaskIntoConstraints = false + containerView.addSubview(indexLabel) + contentView.addSubview(containerView) + containerView.pinToSuperView() + indexLabel.pinToSuperView() + indexLabel.widthAnchor.constraint(greaterThanOrEqualToConstant: VDSLayout.Spacing.space5X.value).activate() + contentView.backgroundColor = .clear + containerView.backgroundColor = .clear + indexLabel.backgroundColor = .clear + } + + internal func update(_ selectedIndex: Int, currentIndex: Int, surface: Surface) { + indexLabel.textStyle = selectedIndex == currentIndex ? .boldBodySmall : .bodySmall + indexLabel.text = "\(currentIndex)" + indexLabel.textColor = textColorConfiguration.getColor(surface) + } +} diff --git a/VDS/SupportingFiles/Icons.xcassets/Restricted/pagination-arrow-left.imageset/Contents.json b/VDS/SupportingFiles/Icons.xcassets/Restricted/pagination-arrow-left.imageset/Contents.json new file mode 100644 index 00000000..6c37b40d --- /dev/null +++ b/VDS/SupportingFiles/Icons.xcassets/Restricted/pagination-arrow-left.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "pagination-arrow-left.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/VDS/SupportingFiles/Icons.xcassets/Restricted/pagination-arrow-left.imageset/pagination-arrow-left.svg b/VDS/SupportingFiles/Icons.xcassets/Restricted/pagination-arrow-left.imageset/pagination-arrow-left.svg new file mode 100644 index 00000000..a8c85c97 --- /dev/null +++ b/VDS/SupportingFiles/Icons.xcassets/Restricted/pagination-arrow-left.imageset/pagination-arrow-left.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/VDS/SupportingFiles/Icons.xcassets/Restricted/pagination-arrow-right.imageset/Contents.json b/VDS/SupportingFiles/Icons.xcassets/Restricted/pagination-arrow-right.imageset/Contents.json new file mode 100644 index 00000000..d294d555 --- /dev/null +++ b/VDS/SupportingFiles/Icons.xcassets/Restricted/pagination-arrow-right.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "pagination-arrow-right.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/VDS/SupportingFiles/Icons.xcassets/Restricted/pagination-arrow-right.imageset/pagination-arrow-right.svg b/VDS/SupportingFiles/Icons.xcassets/Restricted/pagination-arrow-right.imageset/pagination-arrow-right.svg new file mode 100644 index 00000000..be02bedf --- /dev/null +++ b/VDS/SupportingFiles/Icons.xcassets/Restricted/pagination-arrow-right.imageset/pagination-arrow-right.svg @@ -0,0 +1,3 @@ + + +