feat: add extended fields (notes, deadline, priority) to todo editor

This commit is contained in:
2026-02-12 17:47:36 +01:00
parent 89e149d35f
commit 7738f4b52f
5 changed files with 231 additions and 13 deletions

View File

@@ -0,0 +1,23 @@
import SwiftUI
enum Priority: Int, CaseIterable {
case low = 0
case medium = 1
case high = 2
var label: String {
switch self {
case .low: "Niedrig"
case .medium: "Mittel"
case .high: "Hoch"
}
}
var color: Color {
switch self {
case .low: .green
case .medium: .yellow
case .high: .red
}
}
}

View File

@@ -5,11 +5,28 @@ struct TodoItem: Identifiable {
var title: String
var isCompleted: Bool
let createdAt: Date
var notes: String?
var deadline: Date?
var priority: Priority?
var modifiedAt: Date?
init(id: UUID = UUID(), title: String, isCompleted: Bool = false, createdAt: Date = Date()) {
init(
id: UUID = UUID(),
title: String,
isCompleted: Bool = false,
createdAt: Date = Date(),
notes: String? = nil,
deadline: Date? = nil,
priority: Priority? = nil,
modifiedAt: Date? = nil
) {
self.id = id
self.title = title
self.isCompleted = isCompleted
self.createdAt = createdAt
self.notes = notes
self.deadline = deadline
self.priority = priority
self.modifiedAt = modifiedAt
}
}

View File

@@ -23,9 +23,15 @@ class ListStore {
lists.removeAll { $0.id == list.id }
}
func addItem(to listID: UUID, title: String) {
func addItem(
to listID: UUID,
title: String,
notes: String? = nil,
deadline: Date? = nil,
priority: Priority? = nil
) {
guard let index = lists.firstIndex(where: { $0.id == listID }) else { return }
let item = TodoItem(title: title)
let item = TodoItem(title: title, notes: notes, deadline: deadline, priority: priority)
lists[index].items.append(item)
}
@@ -34,15 +40,31 @@ class ListStore {
lists[listIndex].items.removeAll { $0.id == itemID }
}
func updateItem(_ itemID: UUID, in listID: UUID, title: String) {
func updateItem(
_ itemID: UUID,
in listID: UUID,
title: String,
notes: String? = nil,
deadline: Date? = nil,
priority: Priority? = nil,
isCompleted: Bool? = nil
) {
guard let listIndex = lists.firstIndex(where: { $0.id == listID }),
let itemIndex = lists[listIndex].items.firstIndex(where: { $0.id == itemID }) else { return }
lists[listIndex].items[itemIndex].title = title
lists[listIndex].items[itemIndex].notes = notes
lists[listIndex].items[itemIndex].deadline = deadline
lists[listIndex].items[itemIndex].priority = priority
if let isCompleted {
lists[listIndex].items[itemIndex].isCompleted = isCompleted
}
lists[listIndex].items[itemIndex].modifiedAt = Date()
}
func toggleItemCompleted(_ itemID: UUID, in listID: UUID) {
guard let listIndex = lists.firstIndex(where: { $0.id == listID }),
let itemIndex = lists[listIndex].items.firstIndex(where: { $0.id == itemID }) else { return }
lists[listIndex].items[itemIndex].isCompleted.toggle()
lists[listIndex].items[itemIndex].modifiedAt = Date()
}
}

View File

@@ -8,16 +8,94 @@ struct TodoEditorView: View {
var item: TodoItem?
@State private var title: String = ""
@State private var notes: String = ""
@State private var deadline: Date = Date()
@State private var priority: Priority = .medium
@State private var showNotes: Bool = false
@State private var showDeadline: Bool = false
@State private var showPriority: Bool = false
@FocusState private var titleFocused: Bool
private var isEditing: Bool { item != nil }
private var hasHiddenFields: Bool {
!showNotes || !showDeadline || !showPriority
}
var body: some View {
NavigationStack {
Form {
// Titel
Section {
TextField("Titel", text: $title)
.focused($titleFocused)
}
// Notiz
if showNotes {
Section {
TextField("Notiz hinzufügen...", text: $notes, axis: .vertical)
.lineLimit(3...6)
} header: {
sectionHeader("Notiz", show: $showNotes)
}
}
// Deadline
if showDeadline {
Section {
DatePicker("Fällig am", selection: $deadline, displayedComponents: [.date, .hourAndMinute])
} header: {
sectionHeader("Deadline", show: $showDeadline)
}
}
// Prioritaet
if showPriority {
Section {
Picker("Priorität", selection: $priority) {
ForEach(Priority.allCases, id: \.self) { p in
Label(p.label, systemImage: "circle.fill")
.foregroundStyle(p.color)
.tag(p)
}
}
} header: {
sectionHeader("Priorität", show: $showPriority)
}
}
// Hinzufuegen
if hasHiddenFields {
Section {
if !showNotes {
addFieldButton("Notiz", systemImage: "note.text") { showNotes = true }
}
if !showDeadline {
addFieldButton("Deadline", systemImage: "calendar") { showDeadline = true }
}
if !showPriority {
addFieldButton("Priorität", systemImage: "flag") { showPriority = true }
}
} header: {
Text("Hinzufügen")
}
}
// Meta
if isEditing, let item {
Section {
LabeledContent("Erstellt am", value: item.createdAt.formatted(date: .abbreviated, time: .shortened))
if let modifiedAt = item.modifiedAt {
LabeledContent("Geändert am", value: modifiedAt.formatted(date: .abbreviated, time: .shortened))
}
} header: {
Text("Info")
}
}
}
.navigationTitle(isEditing ? "Bearbeiten" : "Neuer Eintrag")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
@@ -32,20 +110,76 @@ struct TodoEditorView: View {
.onAppear {
if let item {
title = item.title
if let n = item.notes, !n.isEmpty {
notes = n
showNotes = true
}
if let d = item.deadline {
deadline = d
showDeadline = true
}
if let p = item.priority {
priority = p
showPriority = true
}
}
titleFocused = true
}
}
}
// Helpers
private func sectionHeader(_ label: String, show: Binding<Bool>) -> some View {
HStack {
Text(label)
Spacer()
Button {
withAnimation { show.wrappedValue = false }
} label: {
Image(systemName: "xmark.circle.fill")
.foregroundStyle(.secondary)
}
}
}
private func addFieldButton(_ label: String, systemImage: String, action: @escaping () -> Void) -> some View {
Button {
withAnimation { action() }
} label: {
Label(label, systemImage: systemImage)
.font(.callout)
}
}
private func save() {
let trimmed = title.trimmingCharacters(in: .whitespaces)
guard !trimmed.isEmpty else { return }
let finalNotes: String? = showNotes && !notes.trimmingCharacters(in: .whitespaces).isEmpty
? notes.trimmingCharacters(in: .whitespaces)
: nil
let finalDeadline: Date? = showDeadline ? deadline : nil
let finalPriority: Priority? = showPriority ? priority : nil
if let item {
store.updateItem(item.id, in: listID, title: trimmed)
store.updateItem(
item.id,
in: listID,
title: trimmed,
notes: finalNotes,
deadline: finalDeadline,
priority: finalPriority
)
} else {
store.addItem(to: listID, title: trimmed)
store.addItem(
to: listID,
title: trimmed,
notes: finalNotes,
deadline: finalDeadline,
priority: finalPriority
)
}
dismiss()
}

View File

@@ -15,13 +15,35 @@ struct TodoRowView: View {
.buttonStyle(.borderless)
Button(action: { onTap?() }) {
VStack(alignment: .leading, spacing: 2) {
Text(item.title)
.strikethrough(item.isCompleted)
.foregroundStyle(item.isCompleted ? .secondary : .primary)
if let notes = item.notes, !notes.isEmpty {
Text(noteSnippet(notes))
.font(.caption)
.foregroundStyle(.secondary)
.lineLimit(1)
}
}
.frame(maxWidth: .infinity, alignment: .leading)
.contentShape(Rectangle())
}
.buttonStyle(.plain)
if let priority = item.priority {
Image(systemName: "circle.fill")
.font(.system(size: 8))
.foregroundStyle(priority.color)
}
}
}
private func noteSnippet(_ text: String) -> String {
if text.count > 50 {
return String(text.prefix(50)) + "..."
}
return text
}
}