Digital ACT-191 ONEAPP-7958 story: added action for next and previous on changing min / max dates

This commit is contained in:
vasavk 2024-05-09 09:43:13 +05:30
parent 80f90e0b56
commit 450dd1aae8
5 changed files with 139 additions and 80 deletions

View File

@ -79,7 +79,8 @@ open class CalendarBase: View {
private var selectedIndexPath : IndexPath? private var selectedIndexPath : IndexPath?
private var dates: [Date] = [] private var dates: [Date] = []
private var days: [String] = [] private var days: [String] = []
private var displayDate: Date = Date()
internal var containerView = View().with { internal var containerView = View().with {
$0.clipsToBounds = true $0.clipsToBounds = true
} }
@ -146,8 +147,14 @@ open class CalendarBase: View {
open override func updateView() { open override func updateView() {
super.updateView() super.updateView()
self.fetchDates(with: selectedDate) // range check between min & max dates
collectionView.reloadData() 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 layer.backgroundColor = backgroundColorConfiguration.getColor(self).cgColor
if hideContainerBorder { if hideContainerBorder {
layer.borderColor = nil layer.borderColor = nil
@ -171,12 +178,12 @@ open class CalendarBase: View {
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Private Methods // MARK: - Private Methods
//-------------------------------------------------- //--------------------------------------------------
func fetchDates(with date:Date) { func fetchDates(with aDate:Date) {
days.removeAll() days.removeAll()
self.dates = selectedDate.calendarDisplayDays self.dates = aDate.calendarDisplayDays
for date in dates { for date in dates {
// code to be executed // code to be executed
if date.monthInt != selectedDate.monthInt { if date.monthInt != aDate.monthInt {
days.append("") days.append("")
} else { } else {
days.append(getDay(with: date)) 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 } if (self.days[indexPath.row] == self.getDay(with: selectedDate)) { selectedIndexPath = indexPath }
return cell 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 { guard let header = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: CalendarHeaderReusableView.identifier, for: indexPath) as? CalendarHeaderReusableView else {
return UICollectionReusableView() 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 return header
} else { } else {
// Footer // Footer

View File

@ -94,12 +94,12 @@ final class CalendarDateCollectionViewCell: UICollectionViewCell {
/// Updating UI based on selected date, modified indicators data along with surface /// 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 /// 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.surface = surface
numberLabel.text = text numberLabel.text = text
stackView.arrangedSubviews.forEach { $0.removeFromSuperview() } 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 { if let day:Int = Int(numberLabel.text), day < minDate.dayInt || day > maxDate.dayInt {
numberLabel.isEnabled = false numberLabel.isEnabled = false
numberLabel.textColor = disabledTextColorConfiguration.getColor(surface) numberLabel.textColor = disabledTextColorConfiguration.getColor(surface)
@ -121,16 +121,18 @@ final class CalendarDateCollectionViewCell: UICollectionViewCell {
// handling inactive dates // handling inactive dates
if inactiveDates.count > 0 { if inactiveDates.count > 0 {
for x in (0...(inactiveDates.count-1)) { for x in (0...(inactiveDates.count-1)) {
if let day:Int = Int(numberLabel.text), day == inactiveDates[x].dayInt { if (activeDates[x].monthInt == displayDate.monthInt) && (activeDates[x].yearInt == displayDate.yearInt) {
numberLabel.isEnabled = false if let day:Int = Int(numberLabel.text), day == inactiveDates[x].dayInt {
numberLabel.textColor = disabledTextColorConfiguration.getColor(surface) numberLabel.isEnabled = false
layer.backgroundColor = disabledBackgroundColor.getColor(surface).cgColor numberLabel.textColor = disabledTextColorConfiguration.getColor(surface)
layer.backgroundColor = disabledBackgroundColor.getColor(surface).cgColor
}
} }
} }
} }
// update text color, bg color, corner radius // 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) numberLabel.textColor = selectedTextColorConfiguration.getColor(surface)
layer.backgroundColor = selectedBackgroundColor.getColor(surface).cgColor layer.backgroundColor = selectedBackgroundColor.getColor(surface).cgColor
layer.cornerRadius = VDSFormControls.borderRadius layer.cornerRadius = VDSFormControls.borderRadius
@ -142,8 +144,6 @@ final class CalendarDateCollectionViewCell: UICollectionViewCell {
// add indicators // add indicators
if indicatorCount > 0 { 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)) { for x in (0...(indicators.count-1)) {
if (self.numberLabel.text == self.getDay(with: indicators[x].date)) { if (self.numberLabel.text == self.getDay(with: indicators[x].date)) {
let color = (numberLabel.text == self.getDay(with: selectedDate)) ? selectedCellIndicatorColorConfiguration.getColor(surface) : unselectedCellIndicatorColorConfiguration.getColor(surface) 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 // 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 numberLabel.textStyle = hideDate ? .bodySmall : .boldBodySmall
} else { } else {
numberLabel.textStyle = .bodySmall numberLabel.textStyle = .bodySmall

View File

@ -10,25 +10,19 @@ import UIKit
import VDSTokens 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
open class CalendarHeaderView: View { class CalendarHeaderReusableView: UICollectionReusableView {
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
required public init() {
super.init(frame: .zero)
}
public override init(frame: CGRect) { ///Identifier for the Calendar Header Reusable View
super.init(frame: .zero) static let identifier: String = String(describing: CalendarHeaderReusableView.self)
}
public required init?(coder: NSCoder) {
super.init(coder: coder)
}
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Public Properties // 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 // MARK: - Private Properties
@ -64,6 +58,9 @@ open class CalendarHeaderView: View {
return collectionView return collectionView
}() }()
private var surface: Surface = .light
private var displayDate: Date = Date()
internal var previousMonthView = View() internal var previousMonthView = View()
internal var nextMonthView = View() internal var nextMonthView = View()
let viewSize = 40.0 let viewSize = 40.0
@ -84,7 +81,7 @@ open class CalendarHeaderView: View {
$0.size = .small $0.size = .small
} }
internal var monthYearLabel = Label().with { internal var headerTitle = Label().with {
$0.translatesAutoresizingMaskIntoConstraints = false $0.translatesAutoresizingMaskIntoConstraints = false
$0.textAlignment = .center $0.textAlignment = .center
$0.numberOfLines = 1 $0.numberOfLines = 1
@ -94,17 +91,23 @@ open class CalendarHeaderView: View {
} }
internal let daysOfWeek = Date.capitalizedFirstLettersOfWeekdays 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() { override init(frame: CGRect) {
super.initialSetup() super.init(frame: frame)
setUp()
} }
open override func setup() { required init?(coder: NSCoder) {
super.setup() super.init(coder: coder)
setUp()
}
/// Configuring the cell with default setup
private func setUp() {
isAccessibilityElement = false isAccessibilityElement = false
// stackview // stackview
@ -114,7 +117,7 @@ open class CalendarHeaderView: View {
.pinBottom(VDSLayout.space1X) .pinBottom(VDSLayout.space1X)
.pinLeading() .pinLeading()
.pinTrailing() .pinTrailing()
.height(containerSize.height) .height(containerSize.height - VDSLayout.space1X)
.width(containerSize.width) .width(containerSize.width)
stackView.centerXAnchor.constraint(equalTo: centerXAnchor).activate() stackView.centerXAnchor.constraint(equalTo: centerXAnchor).activate()
@ -128,43 +131,49 @@ open class CalendarHeaderView: View {
previousMonthView.widthAnchor.constraint(equalToConstant: viewSize).activate() previousMonthView.widthAnchor.constraint(equalToConstant: viewSize).activate()
previousMonthView.addSubview(previousButton) previousMonthView.addSubview(previousButton)
previousButton.pinCenterY().pinCenterX() previousButton.pinCenterY().pinCenterX()
previousButton.onClick = { _ in self.previousButtonClick() }
// month year label // month year label
topHeaderView.addArrangedSubview(monthYearLabel) topHeaderView.addArrangedSubview(headerTitle)
// next button // next button
topHeaderView.addArrangedSubview(nextMonthView) topHeaderView.addArrangedSubview(nextMonthView)
nextMonthView.widthAnchor.constraint(equalToConstant: viewSize).activate() nextMonthView.widthAnchor.constraint(equalToConstant: viewSize).activate()
nextMonthView.addSubview(nextButton) nextMonthView.addSubview(nextButton)
nextButton.pinCenterY().pinCenterX() nextButton.pinCenterY().pinCenterX()
nextButton.onClick = { _ in self.nextButtonClick() }
// days Collection View // days Collection View
stackView.addArrangedSubview(daysCollectionView) stackView.addArrangedSubview(daysCollectionView)
topHeaderView.heightAnchor.constraint(equalToConstant: viewSize).isActive = true topHeaderView.heightAnchor.constraint(equalToConstant: viewSize).isActive = true
daysCollectionView.centerXAnchor.constraint(equalTo: centerXAnchor).activate() daysCollectionView.centerXAnchor.constraint(equalTo: centerXAnchor).activate()
} }
open override func updateView() { /// Updating UI based on next/previous clicks along with surface.
super.updateView() /// Updating UI to enable/disable the next & previous buttons, updating header title
monthYearLabel.surface = surface func update(with surface: Surface, aDate: Date, nextEnabled: Bool, previousEnabled: Bool) {
self.surface = surface
headerTitle.surface = surface
previousButton.surface = surface previousButton.surface = surface
nextButton.surface = surface nextButton.surface = surface
nextButton.isEnabled = nextEnabled
previousButton.isEnabled = previousEnabled
daysCollectionView.reloadData() daysCollectionView.reloadData()
monthYearLabel.text = "May 2024" let labelText = aDate.getMonthName(date: aDate) + " \(aDate.yearInt)"
monthYearLabel.textColor = monthYearLabelTextColorConfiguration.getColor(surface) headerTitle.text = labelText
headerTitle.textColor = headerTitleTextColorConfiguration.getColor(surface)
} }
override open func layoutSubviews() { func nextButtonClick() {
super.layoutSubviews() nextClicked?()
} }
open override func reset() { func previousButtonClick() {
super.reset() previousClicked?()
monthYearLabel.textStyle = .boldBodySmall
} }
} }
extension CalendarHeaderView: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { extension CalendarHeaderReusableView: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return daysOfWeek.count return daysOfWeek.count
@ -172,7 +181,7 @@ extension CalendarHeaderView: UICollectionViewDelegate, UICollectionViewDataSour
public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: collectionViewCell.identifier, for: indexPath) as? collectionViewCell else { return 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 return cell
} }

View File

@ -8,28 +8,6 @@
import UIKit import UIKit
import VDSTokens 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 /// Custom footer view
class CalendarFooterReusableView: UICollectionReusableView { class CalendarFooterReusableView: UICollectionReusableView {

View File

@ -8,7 +8,10 @@
import Foundation import Foundation
extension Date { extension Date {
static var firstDayOfWeek = Calendar.current.firstWeekday static var firstDayOfWeek = Calendar.current.firstWeekday
/// Capitalizes the first letter of the day of the week
static var capitalizedFirstLettersOfWeekdays: [String] { static var capitalizedFirstLettersOfWeekdays: [String] {
let calendar = Calendar.current let calendar = Calendar.current
let weekdays = calendar.shortWeekdaySymbols 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 { var startOfMonth: Date {
Calendar.current.dateInterval(of: .month, for: self)!.start Calendar.current.dateInterval(of: .month, for: self)!.start
} }
@ -28,6 +43,7 @@ extension Date {
return Calendar.current.date(byAdding: .day, value: -1, to: lastDay)! return Calendar.current.date(byAdding: .day, value: -1, to: lastDay)!
} }
/// Get the number of days of the month
var numberOfDaysInMonth: Int { var numberOfDaysInMonth: Int {
Calendar.current.component(.day, from: endOfMonth) Calendar.current.component(.day, from: endOfMonth)
} }
@ -41,6 +57,7 @@ extension Date {
return Calendar.current.date(byAdding: .day, value: -numberFromPreviousMonth, to: startOfMonth)! return Calendar.current.date(byAdding: .day, value: -numberFromPreviousMonth, to: startOfMonth)!
} }
/// Get the days of the month to display
var calendarDisplayDays: [Date] { var calendarDisplayDays: [Date] {
var days: [Date] = [] var days: [Date] = []
// Start with days from the previous month to fill the grid // Start with days from the previous month to fill the grid
@ -58,11 +75,30 @@ extension Date {
return days 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 { var monthInt: Int {
Calendar.current.component(.month, from: self) Calendar.current.component(.month, from: self)
} }
/// Returns the day value of the given date
var dayInt: Int { var dayInt: Int {
Calendar.current.component(.day, from: self) 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]
}
} }