Files
WorterBuch/WorterBuch/VocabularyGridView.swift

262 lines
9.0 KiB
Swift

//
// VocabularyGridView.swift
// WorterBuch
//
// Created by Oliver Hnát on 01.12.2025.
//
import SwiftUI
import PencilKit
import CoreData
struct VocabularyGridView: View {
@Environment(\.managedObjectContext) private var viewContext
@FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \VocabularyEntry.timestamp, ascending: false)],
animation: .default
)
private var entries: FetchedResults<VocabularyEntry>
@State private var searchText = ""
@State private var selectedEntry: VocabularyEntry?
@State private var selectedFieldType: FieldType?
@State private var showingFieldEditor = false
// Temporary bindings for editing
@State private var editingDrawing: PKDrawing = PKDrawing()
@State private var editingText: String = ""
var filteredEntries: [VocabularyEntry] {
if searchText.isEmpty {
return Array(entries)
} else {
return entries.filter { entry in
(entry.germanWordText?.localizedCaseInsensitiveContains(searchText) ?? false) ||
(entry.germanExplanationText?.localizedCaseInsensitiveContains(searchText) ?? false) ||
(entry.englishTranslationText?.localizedCaseInsensitiveContains(searchText) ?? false)
}
}
}
var body: some View {
NavigationView {
VStack(spacing: 0) {
// Search bar
HStack {
Image(systemName: "magnifyingglass")
.foregroundColor(.secondary)
TextField("Search words, explanations, or translations", text: $searchText)
.textFieldStyle(.plain)
if !searchText.isEmpty {
Button(action: { searchText = "" }) {
Image(systemName: "xmark.circle.fill")
.foregroundColor(.secondary)
}
}
}
.padding(10)
.background(Color(.systemGray6))
.cornerRadius(10)
.padding()
// Column headers
HStack(spacing: 12) {
Text("German Word")
.font(.headline)
.frame(maxWidth: .infinity, alignment: .leading)
Text("German Explanation")
.font(.headline)
.frame(maxWidth: .infinity, alignment: .leading)
Text("English Translation")
.font(.headline)
.frame(maxWidth: .infinity, alignment: .leading)
}
.padding(.horizontal)
.padding(.bottom, 8)
Divider()
// Entries list
List {
ForEach(filteredEntries, id: \.id) { entry in
VocabularyEntryRow(
entry: entry,
onSelectField: { fieldType in
openFieldEditor(for: entry, fieldType: fieldType)
},
onLongPress: { fieldType in
showDefinition(for: entry, fieldType: fieldType)
}
)
.listRowInsets(EdgeInsets(top: 6, leading: 12, bottom: 6, trailing: 12))
.listRowSeparator(.hidden)
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
Button(role: .destructive) {
deleteEntry(entry)
} label: {
Label("Delete", systemImage: "trash")
}
}
}
}
.listStyle(.plain)
}
.navigationTitle("Wörterbuch")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button(action: addEntry) {
Label("Add Entry", systemImage: "plus")
}
}
}
.sheet(isPresented: $showingFieldEditor) {
if let entry = selectedEntry, let fieldType = selectedFieldType {
FieldEditorView(
drawing: bindingForDrawing(entry: entry, fieldType: fieldType),
text: bindingForText(entry: entry, fieldType: fieldType),
fieldType: fieldType
)
.onDisappear {
saveContext()
}
}
}
}
.navigationViewStyle(.stack)
}
private func openFieldEditor(for entry: VocabularyEntry, fieldType: FieldType) {
selectedEntry = entry
selectedFieldType = fieldType
showingFieldEditor = true
}
private func bindingForDrawing(entry: VocabularyEntry, fieldType: FieldType) -> Binding<PKDrawing> {
Binding(
get: {
switch fieldType {
case .germanWord:
return entry.germanWordPKDrawing
case .germanExplanation:
return entry.germanExplanationPKDrawing
case .englishTranslation:
return entry.englishTranslationPKDrawing
}
},
set: { newValue in
switch fieldType {
case .germanWord:
entry.germanWordPKDrawing = newValue
case .germanExplanation:
entry.germanExplanationPKDrawing = newValue
case .englishTranslation:
entry.englishTranslationPKDrawing = newValue
}
}
)
}
private func bindingForText(entry: VocabularyEntry, fieldType: FieldType) -> Binding<String> {
Binding(
get: {
switch fieldType {
case .germanWord:
return entry.germanWordText ?? ""
case .germanExplanation:
return entry.germanExplanationText ?? ""
case .englishTranslation:
return entry.englishTranslationText ?? ""
}
},
set: { newValue in
switch fieldType {
case .germanWord:
entry.germanWordText = newValue
case .germanExplanation:
entry.germanExplanationText = newValue
case .englishTranslation:
entry.englishTranslationText = newValue
}
}
)
}
private func addEntry() {
withAnimation {
let _ = VocabularyEntry.create(in: viewContext)
saveContext()
}
}
private func deleteEntry(_ entry: VocabularyEntry) {
withAnimation {
viewContext.delete(entry)
saveContext()
}
}
private func saveContext() {
do {
try viewContext.save()
} catch {
let nsError = error as NSError
print("Error saving context: \(nsError), \(nsError.userInfo)")
}
}
private func showDefinition(for entry: VocabularyEntry, fieldType: FieldType) {
let text: String
switch fieldType {
case .germanWord:
text = entry.germanWordText ?? ""
case .germanExplanation:
text = entry.germanExplanationText ?? ""
case .englishTranslation:
text = entry.englishTranslationText ?? ""
}
if !text.isEmpty {
DictionaryHelper.showDefinition(for: text)
}
}
}
struct VocabularyEntryRow: View {
@ObservedObject var entry: VocabularyEntry
let onSelectField: (FieldType) -> Void
let onLongPress: (FieldType) -> Void
var body: some View {
HStack(spacing: 12) {
// German word
VocabularyFieldCell(
drawing: entry.germanWordPKDrawing,
text: entry.germanWordText ?? "",
onTap: { onSelectField(.germanWord) },
onLongPress: { onLongPress(.germanWord) }
)
.frame(maxWidth: .infinity)
// German explanation
VocabularyFieldCell(
drawing: entry.germanExplanationPKDrawing,
text: entry.germanExplanationText ?? "",
onTap: { onSelectField(.germanExplanation) },
onLongPress: { onLongPress(.germanExplanation) }
)
.frame(maxWidth: .infinity)
// English translation
VocabularyFieldCell(
drawing: entry.englishTranslationPKDrawing,
text: entry.englishTranslationText ?? "",
onTap: { onSelectField(.englishTranslation) },
onLongPress: { onLongPress(.englishTranslation) }
)
.frame(maxWidth: .infinity)
}
}
}