wordeck-native/Widgets/WordeckWidget/DueWidgetView.swift
Till JS 542082772a refactor(big-bang): cards-native → wordeck-native
Code + Identity-Rename zur Vorbereitung auf Apple-Dev-Portal-Aktion
(Bundle ev.mana.wordeck, App-Group group.ev.mana.wordeck, AASA
applinks:wordeck.com). Build bleibt funktional, aber gegen die
neue text-only-API können image-occlusion-Creates 422 zurückgeben —
das wird mit der Wordeck-Native v1.0-Welle (parallele Apple-Aktion)
sauber gemacht.

Umbenennung:
- 41 Files: cardecky/Cardecky → wordeck/Wordeck (Display, Strings,
  Kommentare)
- 57 Files: CardsNative → WordeckNative, CardsAPI → WordeckAPI,
  CardsTheme → WordeckTheme, CardsBrand → WordeckBrand, CardsWidget →
  WordeckWidget, CardsDueWidget → WordeckDueWidget
- Bundle-ID ev.mana.cardecky → ev.mana.wordeck (project.yml,
  Info.plist, entitlements, Keychain-Service, App-Group)
- AASA applinks:cardecky.mana.how → applinks:wordeck.com
- API-Base cardecky-api.mana.how → api.wordeck.com
- 10 Files renamed (App-Entry, API-Extensions, Theme, Widget,
  Entitlements, Tests)
- xcodeproj regenerated via xcodegen → WordeckNative.xcodeproj
- MaskRegionsTests.swift gelöscht (image-occlusion entfällt mit
  Wordeck text-only)

Forgejo-Repo git.mana.how/till/cards-native → wordeck-native umbenannt
(Auto-Redirect aktiv). Lokales Verzeichnis Code/cards-native/ bleibt
vorerst — wird beim nächsten Apple-Setup mit Bundle-Test umbenannt.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 23:10:42 +02:00

150 lines
4.9 KiB
Swift

import SwiftUI
import WidgetKit
/// Family-Switch für das Cards-Due-Widget.
struct DueWidgetView: View {
let entry: DueEntry
@Environment(\.widgetFamily) private var family
var body: some View {
Group {
switch family {
case .systemSmall:
smallView
case .systemMedium:
mediumView
case .systemLarge:
largeView
case .accessoryCircular:
circularView
case .accessoryInline:
inlineView
case .accessoryRectangular:
rectangularView
default:
smallView
}
}
}
private var smallView: some View {
VStack(alignment: .leading, spacing: 4) {
Text("\(entry.totalDueCount)")
.font(.system(size: 48, weight: .bold))
Text(entry.totalDueCount == 1 ? "Karte fällig" : "Karten fällig")
.font(.caption)
.foregroundStyle(.secondary)
Spacer()
if let top = entry.topDecks.first {
Text(top.name)
.font(.caption2)
.lineLimit(1)
.foregroundStyle(.primary)
}
}
.frame(maxWidth: .infinity, alignment: .leading)
}
private var mediumView: some View {
HStack(alignment: .top, spacing: 16) {
VStack(alignment: .leading, spacing: 2) {
Text("\(entry.totalDueCount)")
.font(.system(size: 40, weight: .bold))
Text(entry.totalDueCount == 1 ? "Karte fällig" : "Karten fällig")
.font(.caption)
.foregroundStyle(.secondary)
}
Divider()
VStack(alignment: .leading, spacing: 4) {
ForEach(entry.topDecks.prefix(3)) { deck in
HStack {
Text(deck.name)
.font(.caption.weight(.medium))
.lineLimit(1)
Spacer()
Text("\(deck.dueCount)")
.font(.caption.weight(.bold))
}
}
if entry.topDecks.isEmpty {
Text("Keine Decks")
.font(.caption)
.foregroundStyle(.secondary)
}
}
.frame(maxWidth: .infinity, alignment: .leading)
}
}
private var largeView: some View {
VStack(alignment: .leading, spacing: 12) {
HStack(alignment: .firstTextBaseline, spacing: 8) {
Text("\(entry.totalDueCount)")
.font(.system(size: 56, weight: .bold))
.lineLimit(1)
.minimumScaleFactor(0.6)
VStack(alignment: .leading, spacing: 2) {
Text(entry.totalDueCount == 1 ? "Karte fällig" : "Karten fällig")
.font(.subheadline.weight(.medium))
Text("Heute")
.font(.caption)
.foregroundStyle(.secondary)
}
Spacer()
}
Divider()
VStack(alignment: .leading, spacing: 6) {
Text("Top-Decks")
.font(.caption.weight(.semibold))
.foregroundStyle(.secondary)
ForEach(entry.topDecks.prefix(6)) { deck in
HStack {
Text(deck.name)
.font(.callout)
.lineLimit(1)
Spacer(minLength: 8)
Text("\(deck.dueCount)")
.font(.callout.weight(.semibold))
.foregroundStyle(.secondary)
}
}
if entry.topDecks.isEmpty {
Text("Keine Decks mit fälligen Karten.")
.font(.caption)
.foregroundStyle(.secondary)
}
}
Spacer(minLength: 0)
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
}
private var circularView: some View {
ZStack {
Circle()
.fill(.tint.opacity(0.2))
Text("\(entry.totalDueCount)")
.font(.headline.bold())
}
}
private var inlineView: some View {
Text("Cards: \(entry.totalDueCount) fällig")
}
private var rectangularView: some View {
VStack(alignment: .leading, spacing: 2) {
Text("\(entry.totalDueCount) fällig")
.font(.headline)
if let top = entry.topDecks.first {
Text(top.name)
.font(.caption)
.lineLimit(1)
}
}
}
}