From 83d1f1d542959f4c8cc80726ce4759643a66181c Mon Sep 17 00:00:00 2001 From: kutesir Date: Tue, 7 Jul 2026 00:03:06 +0300 Subject: [PATCH] Calendar year view: continuous TickTick-style multi-year scroll MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- KisaniCal/Views/CalendarView.swift | 57 ++++++++++++++++-------------- 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/KisaniCal/Views/CalendarView.swift b/KisaniCal/Views/CalendarView.swift index b9d58c1..bf80844 100644 --- a/KisaniCal/Views/CalendarView.swift +++ b/KisaniCal/Views/CalendarView.swift @@ -673,41 +673,44 @@ struct CalendarView: View { // MARK: - Year - private var yearView: some View { - let year = cal.component(.year, from: displayMonth) - let months: [Date] = (1...12).compactMap { cal.date(from: DateComponents(year: year, month: $0, day: 1)) } - return VStack(spacing: 0) { - // Year header with navigation (unbounded — go as far forward/back as you like). - HStack { - 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) + // 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. + private var yearRange: [Int] { + let now = cal.component(.year, from: Date()) + return Array((now - 8)...(now + 20)) + } + private var yearView: some View { + let currentYear = cal.component(.year, from: Date()) + return ScrollViewReader { proxy in ScrollView(showsIndicators: false) { - LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible()), GridItem(.flexible())], spacing: 24) { - ForEach(months, id: \.self) { m in - cvMiniMonth(m) + LazyVStack(alignment: .leading, spacing: 28) { + ForEach(yearRange, id: \.self) { yr in + 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) { - withAnimation(KisaniSpring.snappy) { - displayMonth = cal.date(byAdding: .year, value: offset, to: displayMonth) ?? displayMonth + private func yearBlock(_ year: Int) -> some View { + let months: [Date] = (1...12).compactMap { cal.date(from: DateComponents(year: year, month: $0, day: 1)) } + 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) } }