vds_ios/VDS/Components/Pagination/Pagination.swift
2024-03-02 18:26:00 +05:30

229 lines
10 KiB
Swift

//
// 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<Int, Never> = PassthroughSubject()
@Published var onNextTapped: PassthroughSubject<Int, Never> = PassthroughSubject()
@Published var onPageWillChange: PassthroughSubject<Int, Never> = PassthroughSubject()
@Published var onPageChanged: PassthroughSubject<Int, Never> = 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)
}
}