- AppIcon.icon (Icon Composer, blaues W) als App-Icon integriert. In project.yml als Target-Source mit type:file → XcodeGen erkennt .icon nativ als wrapper.icon. Alter forest-grüner Platzhalter (AppIcon.appiconset) entfernt. actool baut Icon für iOS + macOS. - macOS-Build repariert (war pre-existing rot seit β-3/β-5/β-6): iOS-only SwiftUI-Modifier mit #if os(iOS) gegated (textInputAutocapitalization, keyboardType, navigationBarDrawer, tabViewBottomAccessory, .buttonStyle(.glass)); .topBarTrailing → cross-platform .primaryAction; .bottomBar-Toolbar gekapselt; iOS-only Extensions mit platformFilter:iOS an den embed-Deps. - Verifiziert: iOS-Sim + macOS BUILD SUCCEEDED. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
80 lines
2.5 KiB
Swift
80 lines
2.5 KiB
Swift
import SwiftUI
|
|
|
|
/// CSV-Import-Form für den `.csv`-Sub-Modus in `DeckEditorView`. Zeigt
|
|
/// File-Picker-Button, Deck-Namens-Feld und eine Preview-Liste der
|
|
/// erkannten Karten.
|
|
///
|
|
/// State (Datei-Picker-Bool, geparste Rows, Deck-Name) lebt im Parent —
|
|
/// dieser View arbeitet nur über `@Binding`.
|
|
struct CSVImportFormSections: View {
|
|
@Binding var rows: [CSVRow]
|
|
@Binding var deckName: String
|
|
@Binding var showImporter: Bool
|
|
|
|
var body: some View {
|
|
Section {
|
|
Button {
|
|
showImporter = true
|
|
} label: {
|
|
Label(rows.isEmpty ? "CSV-Datei wählen" : "Andere Datei wählen", systemImage: "doc.text")
|
|
}
|
|
} header: {
|
|
Text("Datei")
|
|
} footer: {
|
|
Text("Format pro Zeile: vorne,hinten,typ. Typ-Spalte optional (Default basic).")
|
|
}
|
|
|
|
if !rows.isEmpty {
|
|
Section("Deck-Name") {
|
|
TextField("Deck-Name", text: $deckName)
|
|
#if os(iOS)
|
|
.textInputAutocapitalization(.sentences)
|
|
#endif
|
|
}
|
|
|
|
Section {
|
|
preview
|
|
} header: {
|
|
Text("Vorschau (\(rows.count) Karten)")
|
|
}
|
|
}
|
|
}
|
|
|
|
@ViewBuilder
|
|
private var preview: some View {
|
|
let visible = rows.prefix(8)
|
|
ForEach(Array(visible.enumerated()), id: \.offset) { _, row in
|
|
VStack(alignment: .leading, spacing: 4) {
|
|
Text(row.front)
|
|
.font(.subheadline)
|
|
.lineLimit(2)
|
|
.foregroundStyle(WordeckTheme.foreground)
|
|
Text(row.back)
|
|
.font(.caption)
|
|
.lineLimit(2)
|
|
.foregroundStyle(WordeckTheme.mutedForeground)
|
|
if row.type != .basic {
|
|
Text(typeLabel(row.type))
|
|
.font(.caption2)
|
|
.foregroundStyle(WordeckTheme.primary)
|
|
}
|
|
}
|
|
.padding(.vertical, 2)
|
|
}
|
|
if rows.count > visible.count {
|
|
Text("… und \(rows.count - visible.count) weitere")
|
|
.font(.caption)
|
|
.foregroundStyle(WordeckTheme.mutedForeground)
|
|
}
|
|
}
|
|
|
|
private func typeLabel(_ type: CardType) -> String {
|
|
switch type {
|
|
case .basic: "Einfach"
|
|
case .basicReverse: "Beidseitig"
|
|
case .cloze: "Lückentext"
|
|
case .typing: "Eintippen"
|
|
case .multipleChoice: "Multiple Choice"
|
|
}
|
|
}
|
|
}
|