zitare-native/Widgets/ZitareWidget/ZitareWidgetBundle.swift
Till 0bd59ed148 ζ-0 Setup: Repo-Skelett, iOS-Build grün, Healthz live
- project.yml mit Bundle ev.mana.zitare + Widget + ShareExt-Targets
- ManaSwiftCore (ManaCore + ManaTokens) + ManaSwiftUI (ManaAuthUI)
  als Package-Dependencies via path:
- Pure SwiftUI für Native-Surfaces, WKWebView nur für Lese-Tabs
  (Hybrid-Sonderfall vs cards/memoro/manaspur, dokumentiert im
  Playbook ZITARE_NATIVE_GREENFIELD.md)
- Theme: paper-Variant aus @mana/themes
- ZitareAPI.healthCheck via direct URLSession (öffentlicher
  Endpoint, kein AuthenticatedTransport-Gate)
- 6/6 AppConfigTests + 1/1 UI-Smoke grün auf iPhone 16e Simulator
- Live: zitare-api.mana.how/healthz → HTTP/2 200

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 12:15:22 +02:00

85 lines
2.7 KiB
Swift

import SwiftUI
import WidgetKit
/// Phase ζ-2 Placeholder Widget-Bundle für die WidgetKit-Extension.
///
/// Aufgaben in ζ-2:
///
/// - `DailyQuoteWidget`: deterministisches Zitat des Tages
/// (`hash(date + userSeed) index in snapshot.quotes`).
/// - `RandomQuoteWidget`: bei jedem Timeline-Refresh ein neues
/// Zitat.
/// - Datenquelle: SwiftData unter App-Group `group.ev.mana.zitare`,
/// gefüllt vom `SnapshotSync` in der App.
/// - TimelineProvider mit 24h-Window für Daily, 30min für Random.
/// - Drei Sizes (Small/Medium/Large) plus Lock-Screen-Varianten
/// (Circular, Inline).
@main
struct ZitareWidgetBundle: WidgetBundle {
var body: some Widget {
DailyQuotePlaceholderWidget()
}
}
/// Phase ζ-2 Placeholder. Wird ersetzt durch echte Implementation.
struct DailyQuotePlaceholderWidget: Widget {
let kind = "DailyQuotePlaceholder"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: PlaceholderProvider()) { entry in
PlaceholderEntryView(entry: entry)
}
.configurationDisplayName("Zitat des Tages")
.description("Ein kuratiertes Zitat von Zitare — täglich neu.")
.supportedFamilies([.systemSmall, .systemMedium, .systemLarge])
}
}
struct PlaceholderEntry: TimelineEntry {
let date: Date
let quote: String
let author: String
}
struct PlaceholderProvider: TimelineProvider {
func placeholder(in _: Context) -> PlaceholderEntry {
PlaceholderEntry(date: Date(), quote: "Schweizer bleiben.", author: "Carl Spitteler")
}
func getSnapshot(
in context: Context,
completion: @escaping (PlaceholderEntry) -> Void
) {
completion(placeholder(in: context))
}
func getTimeline(
in context: Context,
completion: @escaping (Timeline<PlaceholderEntry>) -> Void
) {
let entry = placeholder(in: context)
let nextRefresh = Calendar.current.date(byAdding: .hour, value: 24, to: Date()) ?? Date()
completion(Timeline(entries: [entry], policy: .after(nextRefresh)))
}
}
struct PlaceholderEntryView: View {
let entry: PlaceholderEntry
var body: some View {
VStack(alignment: .leading, spacing: 8) {
Text(verbatim: "\u{201E}\(entry.quote)\u{201C}")
.font(.callout)
.fontWeight(.medium)
.lineLimit(4)
Spacer(minLength: 4)
Text(verbatim: "\(entry.author)")
.font(.caption)
.foregroundStyle(.secondary)
}
.padding()
.containerBackground(for: .widget) {
Color(red: 0.95, green: 0.93, blue: 0.88)
}
}
}