Calendar year view: continuous TickTick-style multi-year scroll

Replaces the single paged year (< 2026 >, with dead space below December)
with an infinite vertical scroll where years flow one after another. Opens
anchored on the current year (accent-colored); scroll up for past years,
down for future. Bottom padding clears the floating tab bar / + button.

- yearView: ScrollViewReader + LazyVStack over a year range, scrollTo(current)
- yearBlock(_:): bold year label + 12 mini-months (reuses cvMiniMonth)
- Removed navigateYear + the ◀ year ▶ header (superseded by inline labels)

(Not build-verified — sim runtime removed to reclaim disk; standard SwiftUI
+ existing cvMiniMonth helper.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
kutesir
2026-07-07 00:03:06 +03:00
committed by kutesir
parent a69b54ff19
commit 83d1f1d542

View File

@@ -673,41 +673,44 @@ struct CalendarView: View {
// MARK: - Year // MARK: - Year
private var yearView: some View { // Continuous, TickTick-style: years flow one after another in an infinite
let year = cal.component(.year, from: displayMonth) // scroll. Opens anchored on the current year; scroll up for past years,
let months: [Date] = (1...12).compactMap { cal.date(from: DateComponents(year: year, month: $0, day: 1)) } // down for future ones no paging, no dead space below December.
return VStack(spacing: 0) { private var yearRange: [Int] {
// Year header with navigation (unbounded go as far forward/back as you like). let now = cal.component(.year, from: Date())
HStack { return Array((now - 8)...(now + 20))
Button { navigateYear(-1) } label: { }
Image(systemName: "chevron.left").font(.system(size: 14, weight: .semibold))
.foregroundColor(AppColors.text(cs)).frame(width: 40, height: 40)
}
Spacer()
Text(String(year))
.font(AppFonts.sans(20, weight: .bold)).foregroundColor(AppColors.text(cs))
Spacer()
Button { navigateYear(1) } label: {
Image(systemName: "chevron.right").font(.system(size: 14, weight: .semibold))
.foregroundColor(AppColors.text(cs)).frame(width: 40, height: 40)
}
}
.padding(.horizontal, 16).padding(.top, 4)
private var yearView: some View {
let currentYear = cal.component(.year, from: Date())
return ScrollViewReader { proxy in
ScrollView(showsIndicators: false) { ScrollView(showsIndicators: false) {
LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible()), GridItem(.flexible())], spacing: 24) { LazyVStack(alignment: .leading, spacing: 28) {
ForEach(months, id: \.self) { m in ForEach(yearRange, id: \.self) { yr in
cvMiniMonth(m) yearBlock(yr).id(yr)
} }
} }
.padding(16) .padding(.top, 8)
.padding(.bottom, 96) // clear the floating tab bar / + button
} }
.onAppear { proxy.scrollTo(currentYear, anchor: .top) }
} }
} }
private func navigateYear(_ offset: Int) { private func yearBlock(_ year: Int) -> some View {
withAnimation(KisaniSpring.snappy) { let months: [Date] = (1...12).compactMap { cal.date(from: DateComponents(year: year, month: $0, day: 1)) }
displayMonth = cal.date(byAdding: .year, value: offset, to: displayMonth) ?? displayMonth let isCurrent = year == cal.component(.year, from: Date())
return VStack(alignment: .leading, spacing: 14) {
Text(String(year))
.font(AppFonts.sans(26, weight: .bold))
.foregroundColor(isCurrent ? AppColors.accent : AppColors.text(cs))
.padding(.horizontal, 16)
LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible()), GridItem(.flexible())], spacing: 24) {
ForEach(months, id: \.self) { m in
cvMiniMonth(m)
}
}
.padding(.horizontal, 16)
} }
} }