Calendarr-IOS wird mit vollstaendiger Historie als Unterverzeichnis ios/ eingebunden. Damit liegen Server, Web, Android und iOS in einem gemeinsamen Repo. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
143 lines
6.2 KiB
Swift
143 lines
6.2 KiB
Swift
import Foundation
|
|
@_spi(Writer) import CalendarrCore
|
|
#if canImport(WidgetKit)
|
|
import WidgetKit
|
|
#endif
|
|
|
|
// The snapshot types and the App Group plumbing now live in CalendarrCore, so
|
|
// that a second app from this team reads the exact same format instead of
|
|
// reimplementing it and drifting.
|
|
//
|
|
// These aliases keep the ~70 existing call sites in the app and the widget
|
|
// compiling unchanged. They are a migration convenience, not a design: new code
|
|
// should use the CalendarrCore names directly.
|
|
typealias WidgetEvent = SnapshotEvent
|
|
typealias WidgetCalendar = SnapshotCalendar
|
|
typealias WidgetSnapshot = CalendarrSnapshot
|
|
|
|
/// The App Group identifier for this platform. See `CalendarrAppGroup`.
|
|
let widgetAppGroupID = CalendarrAppGroup.current
|
|
|
|
extension CalendarrSnapshot {
|
|
// The six colours are stored flat on the wire for backwards compatibility
|
|
// but exposed as `theme` in the package. These keep the widget views, which
|
|
// read them individually, from having to change.
|
|
var todayColorHex: String { theme.today }
|
|
var textColorHex: String { theme.text }
|
|
var backgroundColorHex: String { theme.background }
|
|
var lineColorHex: String { theme.line }
|
|
var primaryColorHex: String { theme.primary }
|
|
var accentColorHex: String { theme.accent }
|
|
}
|
|
|
|
/// Thin facade over `CalendarrCore.SnapshotStore`, preserving the static API the
|
|
/// app and widget already use. Errors are swallowed here exactly as before —
|
|
/// a failed widget cache write must never interrupt the user — but the store now
|
|
/// reports *why* a read failed, which is what `read()` discards and callers that
|
|
/// need the distinction should use `SnapshotStore` directly for.
|
|
enum WidgetStore {
|
|
private static let store = SnapshotStore()
|
|
private static let sessions = SharedSessionStore()
|
|
|
|
/// Version of the app writing the cache, for diagnostics in the consumer.
|
|
private static var writerVersion: String {
|
|
Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "unknown"
|
|
}
|
|
|
|
static func write(_ snapshot: WidgetSnapshot) {
|
|
try? store.write(snapshot)
|
|
}
|
|
|
|
static func read() -> WidgetSnapshot? {
|
|
store.read().snapshot
|
|
}
|
|
|
|
static func writeCalendars(_ calendars: [WidgetCalendar]) {
|
|
try? store.writeCalendars(calendars)
|
|
}
|
|
|
|
static func readCalendars() -> [WidgetCalendar] {
|
|
store.readCalendars()
|
|
}
|
|
|
|
/// Record which server we are pointed at and whether anyone is signed in, so
|
|
/// a reading app can tell "signed out" from "never ran" without reaching into
|
|
/// this app's UserDefaults, which lives in another sandbox.
|
|
static func writeSession(baseURL: String, username: String, isLoggedIn: Bool) {
|
|
try? sessions.write(SharedSession(baseURL: baseURL,
|
|
username: username,
|
|
isLoggedIn: isLoggedIn,
|
|
writtenAt: Date()))
|
|
}
|
|
|
|
/// Forget the session entirely. Used on server reset, where the app returns
|
|
/// to unconfigured and there is no longer a server worth naming.
|
|
static func clearSession() {
|
|
sessions.clear()
|
|
}
|
|
|
|
/// Drop the cached calendar. Called on sign-out and server reset: the
|
|
/// container outlives the session, so without this every reader keeps
|
|
/// rendering the previous user's events.
|
|
static func clear() {
|
|
store.clear()
|
|
WidgetTimelineNotifier.reload()
|
|
}
|
|
|
|
/// Rewrite the existing snapshot with the latest colour / language values so
|
|
/// widgets pick up an appearance change immediately, without waiting for the
|
|
/// next event sync. No-op when nothing is cached yet.
|
|
static func republishAppearanceOnly() {
|
|
guard let existing = read() else { return }
|
|
let defaults = UserDefaults.standard
|
|
let updated = WidgetSnapshot(
|
|
writtenAt: Date(),
|
|
coverageStart: existing.coverageStart,
|
|
coverageEnd: existing.coverageEnd,
|
|
isLoggedIn: existing.isLoggedIn,
|
|
writerVersion: writerVersion,
|
|
events: existing.events,
|
|
theme: SnapshotTheme(
|
|
today: defaults.string(forKey: "todayColor") ?? existing.theme.today,
|
|
text: defaults.string(forKey: "textColor") ?? existing.theme.text,
|
|
background: defaults.string(forKey: "backgroundColor") ?? existing.theme.background,
|
|
line: defaults.string(forKey: "lineColor") ?? existing.theme.line,
|
|
primary: defaults.string(forKey: "primaryColor") ?? existing.theme.primary,
|
|
accent: defaults.string(forKey: "accentColor") ?? existing.theme.accent),
|
|
language: defaults.string(forKey: "appLanguage") ?? existing.language)
|
|
write(updated)
|
|
WidgetTimelineNotifier.reload()
|
|
}
|
|
|
|
/// Build a snapshot from the app's current state. Kept here so the coverage
|
|
/// window and the writer version are stamped in exactly one place.
|
|
static func makeSnapshot(events: [WidgetEvent],
|
|
coverageStart: Date,
|
|
coverageEnd: Date) -> WidgetSnapshot {
|
|
let defaults = UserDefaults.standard
|
|
return WidgetSnapshot(
|
|
writtenAt: Date(),
|
|
coverageStart: coverageStart,
|
|
coverageEnd: coverageEnd,
|
|
isLoggedIn: true, // only ever written while signed in
|
|
writerVersion: writerVersion,
|
|
events: events,
|
|
theme: SnapshotTheme(
|
|
today: defaults.string(forKey: "todayColor") ?? "#4285f4",
|
|
text: defaults.string(forKey: "textColor") ?? "#FFFFFF",
|
|
background: defaults.string(forKey: "backgroundColor") ?? "#000000",
|
|
line: defaults.string(forKey: "lineColor") ?? "#3A3A52",
|
|
primary: defaults.string(forKey: "primaryColor") ?? "#4285f4",
|
|
accent: defaults.string(forKey: "accentColor") ?? "#ea4335"),
|
|
language: defaults.string(forKey: "appLanguage") ?? "system")
|
|
}
|
|
}
|
|
|
|
enum WidgetTimelineNotifier {
|
|
static func reload() {
|
|
#if canImport(WidgetKit)
|
|
WidgetCenter.shared.reloadAllTimelines()
|
|
#endif
|
|
}
|
|
}
|