- 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>
85 lines
2.7 KiB
Swift
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)
|
|
}
|
|
}
|
|
}
|