Files
Calendarr-IOS/CalendarrWidgets/TwoWeeksWidgetView.swift
2026-05-25 11:53:02 +02:00

133 lines
5.3 KiB
Swift

import SwiftUI
import WidgetKit
struct TwoWeeksWidgetView: View {
let entry: CalendarrEntry
private var snapshot: WidgetSnapshot? { entry.snapshot }
private var lang: String { snapshot?.language ?? "system" }
private var cal: Calendar {
var c = Calendar(identifier: .gregorian)
c.locale = WidgetL10n.locale(lang)
c.firstWeekday = 2
return c
}
private var weekStart: Date {
cal.date(from: cal.dateComponents([.yearForWeekOfYear, .weekOfYear], from: entry.date)) ?? entry.date
}
private var fortnight: [Date] {
(0..<14).compactMap { cal.date(byAdding: .day, value: $0, to: weekStart) }
}
private var monthHeader: String {
let f = DateFormatter()
f.locale = WidgetL10n.locale(lang)
f.dateFormat = "LLLL yyyy"
return f.string(from: weekStart).uppercased()
}
private var weekdayHeaders: [String] {
let f = DateFormatter(); f.locale = WidgetL10n.locale(lang)
let symbols = f.veryShortWeekdaySymbols ?? cal.veryShortWeekdaySymbols
let start = cal.firstWeekday - 1
return (0..<7).map { symbols[(start + $0) % 7] }
}
var body: some View {
if let s = snapshot {
let primary = Color(widgetHex: s.primaryColorHex)
let accent = Color(widgetHex: s.accentColorHex)
VStack(alignment: .leading, spacing: 2) {
HStack(alignment: .firstTextBaseline, spacing: 4) {
Text(monthHeader.split(separator: " ").first.map(String.init) ?? "")
.font(.system(size: 9, weight: .bold))
.foregroundStyle(primary)
Text(monthHeader.split(separator: " ").dropFirst().joined(separator: " "))
.font(.system(size: 9, weight: .semibold))
}
weekdayRow(accent: accent)
GeometryReader { geo in
let colW = geo.size.width / 7
let rowH = geo.size.height / 2
VStack(spacing: 0) {
ForEach(0..<2, id: \.self) { row in
HStack(spacing: 0) {
ForEach(0..<7, id: \.self) { col in
let day = fortnight[row * 7 + col]
dayCell(day, snapshot: s, primary: primary, accent: accent)
.frame(width: colW, height: rowH)
.overlay(alignment: .trailing) {
if col < 6 {
Rectangle()
.fill(Color(widgetHex: s.lineColorHex).opacity(0.35))
.frame(width: 0.5)
}
}
.overlay(alignment: .top) {
if row == 1 {
Rectangle()
.fill(Color(widgetHex: s.lineColorHex).opacity(0.35))
.frame(height: 0.5)
}
}
}
}
}
}
}
}
} else {
Text(WidgetL10n.t("widget.no_data", lang))
.font(.caption)
.foregroundStyle(.secondary)
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
private func weekdayRow(accent: Color) -> some View {
HStack(spacing: 0) {
ForEach(weekdayHeaders, id: \.self) { h in
Text(h)
.font(.system(size: 7, weight: .bold))
.foregroundStyle(.secondary)
.frame(maxWidth: .infinity)
}
}
}
private func dayCell(_ day: Date,
snapshot: WidgetSnapshot,
primary: Color,
accent: Color) -> some View {
let isToday = cal.isDateInToday(day)
let evs = WidgetHelpers.events(for: day, in: snapshot)
return VStack(alignment: .center, spacing: 0.5) {
Text("\(cal.component(.day, from: day))")
.font(.system(size: 8.5, weight: isToday ? .bold : .semibold))
.foregroundStyle(isToday ? Color.white : Color.primary)
.frame(width: 12, height: 12)
.background(isToday ? primary : Color.clear)
.clipShape(Circle())
// Up to 3 colored dots
HStack(spacing: 1) {
ForEach(evs.prefix(3).indices, id: \.self) { i in
Circle()
.fill(Color(widgetHex: evs[i].colorHex))
.frame(width: 3, height: 3)
}
}
.frame(height: 3)
if evs.count > 3 {
Text("+\(evs.count - 3)")
.font(.system(size: 6))
.foregroundStyle(accent)
}
Spacer(minLength: 0)
}
.padding(.top, 1)
}
}