Merge develop -> main: full session (KC-51 through KC-71) #28
@@ -294,6 +294,8 @@ struct CalendarView: View {
|
||||
}()
|
||||
@State private var viewMode: CalendarViewMode = .month
|
||||
@State private var calTaskListMode: Bool = false
|
||||
/// Year currently at the top of the continuous year scroll (drives the header in .year mode).
|
||||
@State private var visibleYear: Int = Calendar.current.component(.year, from: Date())
|
||||
@State private var showAddTask = false
|
||||
@State private var showCalendarConnect = false
|
||||
@State private var showWorkoutSession = false
|
||||
@@ -333,7 +335,7 @@ struct CalendarView: View {
|
||||
|
||||
// Center: month + year
|
||||
Spacer()
|
||||
Text(monthTitle)
|
||||
Text(headerTitle)
|
||||
.font(AppFonts.sans(17, weight: .semibold))
|
||||
.foregroundColor(AppColors.text(cs))
|
||||
Spacer()
|
||||
@@ -434,6 +436,12 @@ struct CalendarView: View {
|
||||
}
|
||||
}
|
||||
|
||||
/// In the continuous year view the header follows the scrolled-to year;
|
||||
/// otherwise it's the month + year of the displayed month.
|
||||
private var headerTitle: String {
|
||||
viewMode == .year ? String(visibleYear) : monthTitle
|
||||
}
|
||||
|
||||
private var monthTitle: String {
|
||||
let f = DateFormatter(); f.dateFormat = "MMMM yyyy"
|
||||
return f.string(from: displayMonth)
|
||||
@@ -673,6 +681,15 @@ struct CalendarView: View {
|
||||
|
||||
// MARK: - Year
|
||||
|
||||
// Reports each year block's top offset within the year scroll, so the
|
||||
// header can follow whichever year is currently at the top.
|
||||
private struct YearTopKey: PreferenceKey {
|
||||
static var defaultValue: [Int: CGFloat] = [:]
|
||||
static func reduce(value: inout [Int: CGFloat], nextValue: () -> [Int: CGFloat]) {
|
||||
value.merge(nextValue()) { _, new in new }
|
||||
}
|
||||
}
|
||||
|
||||
// Continuous, TickTick-style: years flow one after another in an infinite
|
||||
// scroll. Opens anchored on the current year; scroll up for past years,
|
||||
// down for future ones — no paging, no dead space below December.
|
||||
@@ -693,7 +710,22 @@ struct CalendarView: View {
|
||||
.padding(.top, 8)
|
||||
.padding(.bottom, 96) // clear the floating tab bar / + button
|
||||
}
|
||||
.onAppear { proxy.scrollTo(currentYear, anchor: .top) }
|
||||
.coordinateSpace(name: "yearScroll")
|
||||
.onPreferenceChange(YearTopKey.self) { tops in
|
||||
// The header year is the topmost block that has reached the top edge:
|
||||
// among blocks at/above a small threshold, the one nearest the top.
|
||||
let threshold: CGFloat = 60
|
||||
let reached = tops.filter { $0.value <= threshold }
|
||||
if let y = reached.max(by: { $0.value < $1.value })?.key
|
||||
?? tops.min(by: { $0.value < $1.value })?.key,
|
||||
y != visibleYear {
|
||||
visibleYear = y
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
visibleYear = currentYear
|
||||
proxy.scrollTo(currentYear, anchor: .top)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -712,6 +744,14 @@ struct CalendarView: View {
|
||||
}
|
||||
.padding(.horizontal, 16)
|
||||
}
|
||||
.background(
|
||||
GeometryReader { geo in
|
||||
Color.clear.preference(
|
||||
key: YearTopKey.self,
|
||||
value: [year: geo.frame(in: .named("yearScroll")).minY]
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private func cvMiniMonth(_ month: Date) -> some View {
|
||||
|
||||
Reference in New Issue
Block a user