111 lines
4.8 KiB
Swift
111 lines
4.8 KiB
Swift
import Foundation
|
|
|
|
/// 日清日历的展示模式。
|
|
enum OfflineCollectionCalendarMode: Sendable, Equatable {
|
|
case week
|
|
case month
|
|
}
|
|
|
|
/// 周/月日历日期运算状态,不依赖 UIKit,便于单元测试。
|
|
struct OfflineCollectionCalendarState: Sendable, Equatable {
|
|
private let calendar: Calendar
|
|
private(set) var selectedDate: Date
|
|
private(set) var maximumDate: Date
|
|
private(set) var mode: OfflineCollectionCalendarMode
|
|
|
|
init(
|
|
selectedDate: Date,
|
|
maximumDate: Date,
|
|
mode: OfflineCollectionCalendarMode = .week,
|
|
calendar: Calendar = OfflineCollectionDate.calendar
|
|
) {
|
|
self.calendar = calendar
|
|
self.maximumDate = calendar.startOfDay(for: maximumDate)
|
|
self.selectedDate = min(calendar.startOfDay(for: selectedDate), self.maximumDate)
|
|
self.mode = mode
|
|
}
|
|
|
|
/// 切换周历和月历并保留选中日期。
|
|
mutating func toggleMode() {
|
|
mode = mode == .week ? .month : .week
|
|
}
|
|
|
|
/// 选择不晚于服务端今日的日期。
|
|
mutating func select(_ date: Date) -> Bool {
|
|
let value = calendar.startOfDay(for: date)
|
|
guard value <= maximumDate else { return false }
|
|
selectedDate = value
|
|
return true
|
|
}
|
|
|
|
/// 横滑一个周或一个月,并返回新的选中日期。
|
|
@discardableResult
|
|
mutating func movePage(_ offset: Int) -> Date {
|
|
let candidate: Date
|
|
switch mode {
|
|
case .week:
|
|
candidate = calendar.date(byAdding: .day, value: offset * 7, to: selectedDate) ?? selectedDate
|
|
case .month:
|
|
let current = calendar.dateComponents([.year, .month, .day], from: selectedDate)
|
|
let monthStart = calendar.date(from: DateComponents(year: current.year, month: current.month, day: 1)) ?? selectedDate
|
|
let targetMonth = calendar.date(byAdding: .month, value: offset, to: monthStart) ?? monthStart
|
|
let days = calendar.range(of: .day, in: .month, for: targetMonth)?.count ?? 1
|
|
candidate = calendar.date(byAdding: .day, value: min(current.day ?? 1, days) - 1, to: targetMonth) ?? targetMonth
|
|
}
|
|
selectedDate = min(calendar.startOfDay(for: candidate), maximumDate)
|
|
return selectedDate
|
|
}
|
|
|
|
/// 判断指定方向是否存在不晚于服务端今日的周或月页面。
|
|
func canMovePage(_ offset: Int) -> Bool {
|
|
guard offset != 0 else { return false }
|
|
if offset < 0 { return true }
|
|
switch mode {
|
|
case .week:
|
|
guard let candidate = calendar.date(byAdding: .day, value: offset * 7, to: selectedDate) else { return false }
|
|
let weekday = calendar.component(.weekday, from: candidate)
|
|
let daysFromMonday = (weekday + 5) % 7
|
|
let targetMonday = calendar.date(byAdding: .day, value: -daysFromMonday, to: candidate) ?? candidate
|
|
return calendar.startOfDay(for: targetMonday) <= maximumDate
|
|
case .month:
|
|
let current = calendar.dateComponents([.year, .month], from: selectedDate)
|
|
guard let monthStart = calendar.date(from: current),
|
|
let targetMonth = calendar.date(byAdding: .month, value: offset, to: monthStart) else { return false }
|
|
let maximumMonth = calendar.date(
|
|
from: calendar.dateComponents([.year, .month], from: maximumDate)
|
|
) ?? maximumDate
|
|
return targetMonth <= maximumMonth
|
|
}
|
|
}
|
|
|
|
/// 当前周的周一至周日。
|
|
var weekDates: [Date] {
|
|
let weekday = calendar.component(.weekday, from: selectedDate)
|
|
let daysFromMonday = (weekday + 5) % 7
|
|
let monday = calendar.date(byAdding: .day, value: -daysFromMonday, to: selectedDate) ?? selectedDate
|
|
return (0 ..< 7).compactMap { calendar.date(byAdding: .day, value: $0, to: monday) }
|
|
}
|
|
|
|
/// 当前月份固定六行、周一开周的 42 个日期。
|
|
var monthDates: [Date] {
|
|
let parts = calendar.dateComponents([.year, .month], from: selectedDate)
|
|
let first = calendar.date(from: parts) ?? selectedDate
|
|
let weekday = calendar.component(.weekday, from: first)
|
|
let leading = (weekday + 5) % 7
|
|
let start = calendar.date(byAdding: .day, value: -leading, to: first) ?? first
|
|
return (0 ..< 42).compactMap { calendar.date(byAdding: .day, value: $0, to: start) }
|
|
}
|
|
|
|
/// 当前选中日期所在周在固定六行月历中的零基行号。
|
|
var selectedWeekRowIndex: Int {
|
|
let index = monthDates.firstIndex { calendar.isDate($0, inSameDayAs: selectedDate) } ?? 0
|
|
return index / 7
|
|
}
|
|
|
|
/// 当前选中月份标题。
|
|
var monthTitle: String {
|
|
let parts = calendar.dateComponents([.year, .month], from: selectedDate)
|
|
return "\(parts.year ?? 0)年\(parts.month ?? 0)月"
|
|
}
|
|
}
|