Files
Calendarr-IOS/CalendarrWidgets/CalendarDayWidgetView.swift
Scarriffle 17ebf788ce iOS: widget padding, week event limit, long-press day preview
- CalendarDayWidget: add 6pt top padding to header so content isn't flush
  against the widget edge
- ThisWeekWidget: increase per-day event cap from 6 to 8 to prevent "+1"
  overflow when there is vertical space available
- MonthView: add DayContextPreviewView to the long-press context menu using
  .contextMenu(menuItems:preview:); shows all-day events as colored bars
  with chevron continuation arrows, timed events as dot+time+title rows

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 20:15:53 +02:00

153 lines
5.9 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import SwiftUI
import WidgetKit
struct CalendarDayWidgetView: 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 weekDays: [Date] {
let start = cal.date(from: cal.dateComponents([.yearForWeekOfYear, .weekOfYear], from: entry.date)) ?? entry.date
return (0..<7).compactMap { cal.date(byAdding: .day, value: $0, to: start) }
}
private var upcomingEvents: [WidgetEvent] {
guard let s = snapshot else { return [] }
return WidgetHelpers.upcoming(from: entry.date, daysAhead: 1, in: s)
}
private var monthFmt: DateFormatter {
let f = DateFormatter(); f.locale = WidgetL10n.locale(lang); f.dateFormat = "LLLL"; return f
}
private var weekdayFmt: DateFormatter {
let f = DateFormatter(); f.locale = WidgetL10n.locale(lang); f.dateFormat = "EEEE"; return f
}
private var timeFmt: DateFormatter {
let f = DateFormatter(); f.locale = WidgetL10n.locale(lang); f.dateFormat = "HH:mm"; return f
}
var body: some View {
if let s = snapshot {
let primary = Color(widgetHex: s.primaryColorHex)
let accent = Color(widgetHex: s.accentColorHex)
VStack(alignment: .leading, spacing: 0) {
header(primary: primary)
.padding(.top, 6)
weekStrip(snapshot: s, primary: primary, accent: accent)
.padding(.vertical, 3)
Rectangle()
.fill(Color(widgetHex: s.lineColorHex).opacity(0.4))
.frame(height: 0.5)
.padding(.bottom, 4)
eventList(accent: accent)
}
} else {
Text(WidgetL10n.t("widget.no_data", lang))
.font(.caption)
.foregroundStyle(.secondary)
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
// MARK: Header
private func header(primary: Color) -> some View {
HStack(alignment: .top, spacing: 6) {
Text("\(cal.component(.day, from: entry.date))")
.font(.system(size: 30, weight: .bold))
.foregroundStyle(primary)
.frame(width: 40, alignment: .leading)
.minimumScaleFactor(0.7)
VStack(alignment: .leading, spacing: 1) {
Text(monthFmt.string(from: entry.date).uppercased())
.font(.system(size: 11, weight: .bold))
.foregroundStyle(primary)
Text("\(WidgetL10n.t("widget.today", lang)), \(weekdayFmt.string(from: entry.date))")
.font(.system(size: 10))
.foregroundStyle(.secondary)
.lineLimit(1)
.minimumScaleFactor(0.75)
}
Spacer(minLength: 0)
}
.padding(.bottom, 2)
}
// MARK: Week strip
private func weekStrip(snapshot: WidgetSnapshot, primary: Color, accent: Color) -> some View {
HStack(spacing: 0) {
ForEach(weekDays, id: \.self) { day in
let isToday = cal.isDateInToday(day)
let hasEvs = !WidgetHelpers.events(for: day, in: snapshot).isEmpty
VStack(spacing: 2) {
Text(shortDay(day))
.font(.system(size: 8, weight: .bold))
.foregroundStyle(isToday ? accent : .secondary)
ZStack {
if isToday {
Circle().fill(primary)
} else if hasEvs {
Circle().fill(accent.opacity(0.18))
}
Text("\(cal.component(.day, from: day))")
.font(.system(size: 11, weight: isToday ? .bold : .medium))
.foregroundStyle(isToday ? .white : .primary)
}
.frame(width: 22, height: 22)
}
.frame(maxWidth: .infinity)
}
}
}
private func shortDay(_ date: Date) -> String {
let f = DateFormatter()
f.locale = WidgetL10n.locale(lang)
f.dateFormat = "EEE"
return String(f.string(from: date).prefix(2)).uppercased()
}
// MARK: Event list
@ViewBuilder
private func eventList(accent: Color) -> some View {
if upcomingEvents.isEmpty {
Text(WidgetL10n.t("widget.no_events", lang))
.font(.system(size: 11))
.foregroundStyle(.secondary)
Spacer(minLength: 0)
} else {
VStack(alignment: .leading, spacing: 4) {
ForEach(upcomingEvents.prefix(3)) { ev in
HStack(alignment: .center, spacing: 6) {
RoundedRectangle(cornerRadius: 1.5)
.fill(Color(widgetHex: ev.colorHex))
.frame(width: 3, height: 22)
VStack(alignment: .leading, spacing: 1) {
Text(ev.title)
.font(.system(size: 11, weight: .semibold))
.lineLimit(1)
Text(ev.isAllDay
? WidgetL10n.t("widget.allday", lang)
: "\(timeFmt.string(from: ev.start)) \(timeFmt.string(from: ev.end))")
.font(.system(size: 9))
.foregroundStyle(.secondary)
}
Spacer(minLength: 0)
}
}
Spacer(minLength: 0)
}
}
}
}