Files
Calendarr-IOS/Calendarr iOS/Views/Calendar/AgendaView.swift
Scarriffle 8b3cc11e25 Add localization (DE/EN), vertical-scroll month view, context menus, custom colors
- Vertical-scroll month view with multi-day event spans, zig-zag month
  divider, CW number per week, on-demand event loading while scrolling
- Top bar redesign: icon-only view picker on right, month title centered
- Long-press context menus on day cells (month) and hour slots (week/day)
  for "New event", "Open in week view", "Open in day view", "Open in month view"
- Localization system with system/de/en switch covering top bar, view picker,
  settings, menu, profile, server, accounts, event editor, agenda
- Three new color pickers (text/background/line) + today-marker color
  applied in calendar views; current-time line now uses today color
- App icon: removed alpha channel, accent color set to icon green (#20A050)
- TestFlight: ITSAppUsesNonExemptEncryption=NO baked into Info.plist keys

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 22:00:49 +02:00

112 lines
3.6 KiB
Swift

import SwiftUI
struct AgendaView: View {
let store: CalendarStore
let onEventTap: (CalEvent) -> Void
@AppStorage("appLanguage") private var appLang = "system"
private var cal: Calendar { store.userCalendar }
private var grouped: [(Date, [CalEvent])] {
let start = cal.startOfDay(for: .now)
let end = cal.date(byAdding: .day, value: 90, to: start)!
var dict: [Date: [CalEvent]] = [:]
for ev in store.events(in: start, end: end) {
let key = cal.startOfDay(for: ev.startDate)
dict[key, default: []].append(ev)
}
return dict.keys.sorted().map { ($0, dict[$0]!.sorted { $0.startDate < $1.startDate }) }
}
private var dayFmt: DateFormatter {
let f = DateFormatter()
f.locale = L10n.locale(appLang)
f.dateFormat = "EEEE, d. MMMM yyyy"
return f
}
private var timeFmt: DateFormatter {
let f = DateFormatter()
f.locale = L10n.locale(appLang)
f.timeStyle = .short
f.dateStyle = .none
return f
}
var body: some View {
if grouped.isEmpty {
ContentUnavailableView(
L10n.t("cal.no_events_title", appLang),
systemImage: "calendar",
description: Text(L10n.t("cal.no_events_body", appLang))
)
} else {
List {
ForEach(grouped, id: \.0) { day, evs in
Section {
ForEach(evs) { ev in
Button { onEventTap(ev) } label: {
AgendaEventRow(event: ev, timeFmt: timeFmt, allDayLabel: L10n.t("cal.allday", appLang))
}
.buttonStyle(.plain)
}
} header: {
Text(dayFmt.string(from: day))
.font(.footnote.weight(.semibold))
.foregroundStyle(cal.isDateInToday(day) ? Color.accentColor : .secondary)
}
}
}
.listStyle(.plain)
}
}
}
private struct AgendaEventRow: View {
let event: CalEvent
let timeFmt: DateFormatter
let allDayLabel: String
var timeString: String {
if event.isAllDay { return allDayLabel }
return timeFmt.string(from: event.startDate)
}
var body: some View {
HStack(spacing: 12) {
RoundedRectangle(cornerRadius: 2)
.fill(Color(hex: event.effectiveColor))
.frame(width: 4, height: 40)
VStack(alignment: .leading, spacing: 3) {
Text(event.title)
.font(.body.weight(.medium))
.foregroundStyle(.primary)
HStack(spacing: 6) {
Text(timeString)
.font(.caption)
.foregroundStyle(.secondary)
if !event.location.isEmpty {
Text("·")
.foregroundStyle(.secondary)
Text(event.location)
.font(.caption)
.foregroundStyle(.secondary)
.lineLimit(1)
}
}
Text(event.calendarName)
.font(.caption2)
.foregroundStyle(.tertiary)
}
Spacer()
Image(systemName: "chevron.right")
.font(.caption)
.foregroundStyle(.tertiary)
}
.padding(.vertical, 4)
}
}