feat: add extended fields (notes, deadline, priority) to todo editor
This commit is contained in:
23
MindDump/Models/Priority.swift
Normal file
23
MindDump/Models/Priority.swift
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,15 +8,93 @@ 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 {
|
||||
TextField("Titel", text: $title)
|
||||
.focused($titleFocused)
|
||||
// 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)
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -15,13 +15,35 @@ struct TodoRowView: View {
|
||||
.buttonStyle(.borderless)
|
||||
|
||||
Button(action: { onTap?() }) {
|
||||
Text(item.title)
|
||||
.strikethrough(item.isCompleted)
|
||||
.foregroundStyle(item.isCompleted ? .secondary : .primary)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.contentShape(Rectangle())
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user