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>
This commit is contained in:
parent
28b20cd934
commit
f664a00b64
12 changed files with 809 additions and 85 deletions
79
Sources/Core/Storage/CachedDeck.swift
Normal file
79
Sources/Core/Storage/CachedDeck.swift
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
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
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue