Merge develop -> main: full session (KC-51 through KC-71) #28

Merged
kutesir merged 64 commits from develop into main 2026-07-12 22:57:14 +00:00
Showing only changes of commit c5285a2e30 - Show all commits

View File

@@ -840,84 +840,28 @@ struct CalendarView: View {
// MARK: - Week // MARK: - Week
// Week: a 7-day timeline (TickTick-style) hours down the left, one column
// per weekday, events as positioned blocks. Reuses the day/3-day time grid.
private var weekView: some View { private var weekView: some View {
HStack(alignment: .top, spacing: 0) { let days = cvWeekDays()
VStack(spacing: 0) { return VStack(spacing: 0) {
HStack(spacing: 0) { cvDayColumnHeader(days)
ForEach(Array(weekdays.enumerated()), id: \.offset) { _, d in .contentShape(Rectangle())
Text(d).font(AppFonts.mono(7, weight: .semibold))
.foregroundColor(AppColors.text3(cs)).frame(maxWidth: .infinity)
}
}
.padding(.horizontal, 6).padding(.top, 10)
LazyVGrid(columns: Array(repeating: GridItem(.flexible(), spacing: 0), count: 7), spacing: 2) {
ForEach(Array(gridDays().enumerated()), id: \.offset) { _, date in
let isTod = cal.isDateInToday(date)
let isSel = cal.isDate(date, inSameDayAs: selectedDate)
let inMon = cal.isDate(date, equalTo: displayMonth, toGranularity: .month)
ZStack {
if isTod { Circle().fill(AppColors.accent).frame(width: 20, height: 20) }
else if isSel { Circle().fill(AppColors.accentSoft).frame(width: 20, height: 20) }
Text("\(cal.component(.day, from: date))")
.font(AppFonts.sans(9, weight: isTod ? .bold : .regular))
.foregroundColor(isTod ? .white : inMon ? AppColors.text(cs) : AppColors.text3(cs))
}
.frame(height: 22)
.onTapGesture { withAnimation(KisaniSpring.micro) { selectedDate = date } }
}
}
.padding(.horizontal, 4)
.gesture(DragGesture(minimumDistance: 40).onEnded { v in .gesture(DragGesture(minimumDistance: 40).onEnded { v in
if v.translation.width < -40 { navigateMonth(1) } if v.translation.width < -40 { navigateWeek(1) }
else if v.translation.width > 40 { navigateMonth(-1) } else if v.translation.width > 40 { navigateWeek(-1) }
}) })
Spacer() Divider().background(AppColors.border(cs))
}
.frame(maxWidth: .infinity)
Rectangle().fill(AppColors.border(cs)).frame(width: 1)
ScrollView(showsIndicators: false) { ScrollView(showsIndicators: false) {
VStack(spacing: 0) { cvTimeGrid(days: days).padding(.bottom, 40)
ForEach(cvWeekDays(), id: \.self) { date in
let isTod = cal.isDateInToday(date)
let isSel = cal.isDate(date, inSameDayAs: selectedDate)
let items = cvAgendaItems(date)
VStack(alignment: .leading, spacing: 5) {
HStack(spacing: 4) {
Text(cvDayFmt.string(from: date))
.font(AppFonts.mono(9, weight: .semibold))
.foregroundColor(isTod ? AppColors.accent : AppColors.text2(cs))
Text("\(cal.component(.day, from: date))")
.font(AppFonts.sans(12, weight: isTod ? .bold : .medium))
.foregroundColor(isTod ? AppColors.accent : AppColors.text(cs))
}
if items.isEmpty {
Text("Free").font(AppFonts.sans(9)).foregroundColor(AppColors.text3(cs))
} else {
ForEach(Array(items.prefix(4).enumerated()), id: \.offset) { _, item in
HStack(spacing: 5) {
RoundedRectangle(cornerRadius: 1.5).fill(item.color)
.frame(width: 2.5, height: 11)
Text(item.title).font(AppFonts.sans(9))
.foregroundColor(AppColors.text2(cs)).lineLimit(1)
}
}
if items.count > 4 {
Text("+\(items.count - 4) more").font(AppFonts.sans(8))
.foregroundColor(AppColors.text3(cs))
}
}
}
.padding(10)
.background(isSel ? AppColors.accentSoft : Color.clear)
.contentShape(Rectangle())
.onTapGesture { withAnimation(KisaniSpring.micro) { selectedDate = date } }
Divider().background(AppColors.border(cs))
}
}
} }
.frame(maxWidth: .infinity) }
}
private func navigateWeek(_ offset: Int) {
withAnimation(KisaniSpring.snappy) {
selectedDate = cal.date(byAdding: .day, value: offset * 7, to: selectedDate) ?? selectedDate
displayMonth = cal.date(from: cal.dateComponents([.year, .month], from: selectedDate)) ?? displayMonth
} }
} }
@@ -997,11 +941,6 @@ struct CalendarView: View {
return items return items
} }
/// Everything happening on a date all-day items first, then timed for compact
/// agenda listings (e.g. the Week column). Recurrence-aware.
private func cvAgendaItems(_ date: Date) -> [CVTimeItem] {
(cvAllDayItems(date) + cvTimeItems(date)).sorted { $0.startMin < $1.startMin }
}
@ViewBuilder @ViewBuilder
private func cvAllDayBand(days: [Date]) -> some View { private func cvAllDayBand(days: [Date]) -> some View {