chore(repo): iOS-App als ios/ ins Hauptrepo uebernehmen
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>
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user