Bring CalendarrKit into the repo and fix the iOS build
The monorepo's Xcode project referenced ../CalendarrKit, but the package was never brought across: it is the one repo that never had a remote, so it existed only on one machine. Added as a subtree at the root, where that existing relative path resolves, so the project needs no change. History is preserved — the commits explaining why the snapshot colours sit flat on the wire and why the package must stay Foundation-only are the kind of thing that cannot be reconstructed later. The iOS target also did not compile on beta, independent of any of this. The attachments section combined a string title with a footer, and SwiftUI has no initializer for that pair: the title then binds as the first positional argument of Section(content:footer:), which is what the three misleading "generic parameter 'Content'" errors were. Rewritten as header:/footer:, the way the reminders section directly above already does it. Carries over the pending change from the archived iOS repo: each calendar source now reports its own failure instead of being swallowed by `try?`, so an absent Home Assistant calendar can finally be diagnosed from the app. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
dccf8671b2
commit
ab0c64a9e1
@@ -68,6 +68,10 @@ class CalendarStore {
|
||||
var syncErrors: [SyncError] = []
|
||||
var weekStartsOnMonday = true
|
||||
var writableCalendars: [WritableCalendar] = []
|
||||
/// Why a source contributed nothing, keyed by source name. Empty when every
|
||||
/// source answered. Surfaced in the event editor, because an empty picker
|
||||
/// with no explanation is indistinguishable from "you own no calendars".
|
||||
var writableCalendarErrors: [String: String] = [:]
|
||||
// When set, the calendar shows the group's combined overlay instead of the
|
||||
// user's own events. nil = personal view.
|
||||
var activeGroup: CalGroup? = nil
|
||||
@@ -634,29 +638,53 @@ class CalendarStore {
|
||||
// MARK: – Writable calendars
|
||||
|
||||
func loadWritableCalendars(api: CalendarrAPI) async {
|
||||
async let localCals = (try? await api.getLocalCalendars()) ?? []
|
||||
async let caldavAccs = (try? await api.getCalDAVAccounts()) ?? []
|
||||
async let googleCals = (try? await api.getGoogleCalendars()) ?? []
|
||||
async let haCals = (try? await api.getHACalendars()) ?? []
|
||||
// Each source is fetched independently and reports its own failure.
|
||||
// `try?` used to discard all four, so a source that errored looked
|
||||
// exactly like a source with nothing in it — which is how a missing
|
||||
// Home Assistant calendar can go unexplained for a long time.
|
||||
var errors: [String: String] = [:]
|
||||
|
||||
async let localResult = Self.attempt("local") { try await api.getLocalCalendars() }
|
||||
async let caldavResult = Self.attempt("caldav") { try await api.getCalDAVAccounts() }
|
||||
async let googleResult = Self.attempt("google") { try await api.getGoogleCalendars() }
|
||||
async let haResult = Self.attempt("homeassistant"){ try await api.getHACalendars() }
|
||||
|
||||
let (localCals, localErr) = await localResult
|
||||
let (caldavAccs, caldavErr) = await caldavResult
|
||||
let (googleCals, googleErr) = await googleResult
|
||||
let (haCals, haErr) = await haResult
|
||||
for (source, message) in [("local", localErr), ("caldav", caldavErr),
|
||||
("google", googleErr), ("homeassistant", haErr)] {
|
||||
if let message { errors[source] = message }
|
||||
}
|
||||
|
||||
var result: [WritableCalendar] = []
|
||||
// Skip read-only shared calendars — offering them in the event editor
|
||||
// only leads to a 403 on save. Own + read_write (incl. group) stay.
|
||||
for cal in await localCals where cal.owned || cal.permission == "read_write" {
|
||||
for cal in localCals where cal.owned || cal.permission == "read_write" {
|
||||
result.append(WritableCalendar(id: "local-\(cal.id)", name: cal.name, color: cal.color, source: "local", numericId: cal.id))
|
||||
}
|
||||
for acc in await caldavAccs where acc.enabled {
|
||||
for acc in caldavAccs where acc.enabled {
|
||||
for cal in acc.calendars ?? [] where cal.enabled {
|
||||
result.append(WritableCalendar(id: "caldav-\(cal.id)", name: "\(acc.name) – \(cal.name)", color: cal.color ?? acc.color, source: "caldav", numericId: cal.id))
|
||||
}
|
||||
}
|
||||
for (email, id, name, color) in await googleCals {
|
||||
for (email, id, name, color) in googleCals {
|
||||
result.append(WritableCalendar(id: "google-\(id)", name: "\(email) – \(name)", color: color, source: "google", numericId: id))
|
||||
}
|
||||
for (accName, id, name, color) in await haCals {
|
||||
for (accName, id, name, color) in haCals {
|
||||
result.append(WritableCalendar(id: "ha-\(id)", name: "\(accName) – \(name)", color: color, source: "homeassistant", numericId: id))
|
||||
}
|
||||
writableCalendars = result
|
||||
writableCalendarErrors = errors
|
||||
}
|
||||
|
||||
/// Run one source fetch, returning its result or the reason it produced
|
||||
/// nothing. Never throws: one broken source must not empty the picker.
|
||||
private static func attempt<T>(_ source: String,
|
||||
_ work: () async throws -> [T]) async -> ([T], String?) {
|
||||
do { return (try await work(), nil) }
|
||||
catch { return ([], error.localizedDescription) }
|
||||
}
|
||||
|
||||
// MARK: – Query helpers
|
||||
|
||||
@@ -35,6 +35,18 @@ struct EventEditorSheet: View {
|
||||
private var isEditing: Bool { editingEvent != nil }
|
||||
private var isCopying: Bool { copyFrom != nil && editingEvent == nil }
|
||||
|
||||
/// Sources that failed to answer, named instead of silently leaving their
|
||||
/// calendars out of the picker.
|
||||
@ViewBuilder
|
||||
private var sourceErrorRows: some View {
|
||||
let failures = store.writableCalendarErrors.sorted { $0.key < $1.key }
|
||||
ForEach(failures, id: \.key) { failure in
|
||||
Label("\(failure.key): \(failure.value)", systemImage: "exclamationmark.triangle")
|
||||
.foregroundStyle(.orange)
|
||||
.font(.caption)
|
||||
}
|
||||
}
|
||||
|
||||
private var selectedCal: WritableCalendar? {
|
||||
store.writableCalendars.first { $0.id == selectedCalendarId }
|
||||
}
|
||||
@@ -80,7 +92,9 @@ struct EventEditorSheet: View {
|
||||
Text(L10n.t("event.no_writable", appLang))
|
||||
.foregroundStyle(.secondary)
|
||||
.font(.callout)
|
||||
} else {
|
||||
}
|
||||
sourceErrorRows
|
||||
if !store.writableCalendars.isEmpty {
|
||||
Picker(L10n.t("event.calendar_picker", appLang), selection: $selectedCalendarId) {
|
||||
ForEach(store.writableCalendars) { cal in
|
||||
HStack {
|
||||
@@ -122,7 +136,7 @@ struct EventEditorSheet: View {
|
||||
}
|
||||
.disabled(remindersDisabled)
|
||||
|
||||
Section(L10n.t("event.attachments", appLang)) {
|
||||
Section {
|
||||
ForEach(existingAttachments.filter { !removedAttachmentIds.contains($0.id) }) { att in
|
||||
HStack {
|
||||
Image(systemName: att.symbolName).foregroundStyle(.secondary)
|
||||
@@ -156,6 +170,8 @@ struct EventEditorSheet: View {
|
||||
Label(L10n.t("event.attachment_add", appLang), systemImage: "paperclip")
|
||||
}
|
||||
.disabled(attachmentSlotsUsed >= 10)
|
||||
} header: {
|
||||
Text(L10n.t("event.attachments", appLang))
|
||||
} footer: {
|
||||
Text(L10n.t("event.attachment_hint", appLang))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user