ζ-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>
This commit is contained in:
commit
0bd59ed148
25 changed files with 1468 additions and 0 deletions
85
Widgets/ZitareWidget/ZitareWidgetBundle.swift
Normal file
85
Widgets/ZitareWidget/ZitareWidgetBundle.swift
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue