cards-native/Sources/Core/Storage/CachedDeck.swift
Till JS f664a00b64 v0.2.0 — Phase β-1 Decks lesen
Deck-Liste mit Web-Parität: alle eigenen Decks aus cardecky-api,
Card-/Due-Counts pro Deck (Web-Pattern: separate Calls), Pull-to-
Refresh, Offline-Read via SwiftData, Inbox-Banner für Marketplace-
Forks.

- Deck-Codable-DTO mit snake_case-CodingKeys (DeckCategory,
  DeckVisibility, FsrsSettings)
- ISO8601-Date-Decoder mit Fractional-Seconds-Toleranz
- CardsAPI.listDecks() + cardCount() + dueCount()
- CachedDeck SwiftData-Model mit lastFetchedAt
- DeckListStore (API + Cache, paralleles Counts-Fetching via TaskGroup)
- DeckListView mit forest-Theme, deck.color-Streifen, Inbox-Banner
- AccountView mit Sign-out
- DashboardView durch DeckListView ersetzt
- 6 Unit-Tests + 1 UI-Test grün

Phasen-Plan: mana/docs/playbooks/CARDS_NATIVE_GREENFIELD.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 00:06:28 +02:00

79 lines
2.6 KiB
Swift

import Foundation
import SwiftData
/// Lokales Cache-Model für Decks. Spiegelt das Server-DTO + zwei
/// computed Werte (cardCount, dueCount), die Web pro Deck als zusätzliche
/// API-Calls holt.
///
/// Offline-Read: Liste sichtbar ohne Netz. Server bleibt Wahrheit
/// alle Edits laufen über die API, der Cache wird nur beim Re-Fetch
/// aktualisiert.
@Model
final class CachedDeck {
@Attribute(.unique) var id: String
var userId: String
var name: String
var deckDescription: String?
var color: String?
var categoryRaw: String?
var visibilityRaw: String
var contentHash: String?
var forkedFromMarketplaceDeckId: String?
var forkedFromMarketplaceVersionId: String?
var archivedAt: Date?
var createdAt: Date
var updatedAt: Date
/// Anzahl Karten im Deck (über `/api/v1/cards?deck_id=...`).
var cardCount: Int = 0
/// Anzahl fälliger Reviews (über `/api/v1/reviews/due?deck_id=...`).
var dueCount: Int = 0
/// Zeitpunkt des letzten erfolgreichen Server-Pulls für dieses Deck.
var lastFetchedAt: Date
init(deck: Deck, cardCount: Int = 0, dueCount: Int = 0) {
id = deck.id
userId = deck.userId
name = deck.name
deckDescription = deck.description
color = deck.color
categoryRaw = deck.category?.rawValue
visibilityRaw = deck.visibility.rawValue
contentHash = deck.contentHash
forkedFromMarketplaceDeckId = deck.forkedFromMarketplaceDeckId
forkedFromMarketplaceVersionId = deck.forkedFromMarketplaceVersionId
archivedAt = deck.archivedAt
createdAt = deck.createdAt
updatedAt = deck.updatedAt
self.cardCount = cardCount
self.dueCount = dueCount
lastFetchedAt = .now
}
/// Übernimmt aktualisierte Felder vom Server-DTO.
func update(from deck: Deck, cardCount: Int, dueCount: Int) {
name = deck.name
deckDescription = deck.description
color = deck.color
categoryRaw = deck.category?.rawValue
visibilityRaw = deck.visibility.rawValue
contentHash = deck.contentHash
forkedFromMarketplaceDeckId = deck.forkedFromMarketplaceDeckId
forkedFromMarketplaceVersionId = deck.forkedFromMarketplaceVersionId
archivedAt = deck.archivedAt
updatedAt = deck.updatedAt
self.cardCount = cardCount
self.dueCount = dueCount
lastFetchedAt = .now
}
var category: DeckCategory? {
categoryRaw.flatMap(DeckCategory.init(rawValue:))
}
var isFromMarketplace: Bool {
forkedFromMarketplaceDeckId != nil
}
}