From 9747b2ea67df849f574e7f672e9437e83692515e Mon Sep 17 00:00:00 2001 From: Paul Date: Wed, 4 Mar 2026 19:30:06 +0100 Subject: [PATCH] fix: add inline comments to models, viewmodel and views for better readable code --- MindDump/MindDumpApp.swift | 1 + MindDump/Models/TodoItem.swift | 1 + MindDump/Models/TodoList.swift | 1 + MindDump/ViewModels/ListStore.swift | 13 +++++++++++++ MindDump/Views/MindDumpButton.swift | 1 + MindDump/Views/TodoEditorView.swift | 3 +++ MindDumpTests/MindDumpTests.swift | 18 +++++++++--------- 7 files changed, 29 insertions(+), 9 deletions(-) diff --git a/MindDump/MindDumpApp.swift b/MindDump/MindDumpApp.swift index 314a7bb..1faa3d2 100644 --- a/MindDump/MindDumpApp.swift +++ b/MindDump/MindDumpApp.swift @@ -7,6 +7,7 @@ struct MindDumpApp: App { @State private var store: ListStore init() { + // Use in-memory store for UI tests so they start with a clean database let isUITest = ProcessInfo.processInfo.arguments.contains("UI_TESTING") let config = isUITest ? ModelConfiguration(isStoredInMemoryOnly: true) diff --git a/MindDump/Models/TodoItem.swift b/MindDump/Models/TodoItem.swift index 9187d49..6e4e5ce 100644 --- a/MindDump/Models/TodoItem.swift +++ b/MindDump/Models/TodoItem.swift @@ -13,6 +13,7 @@ class TodoItem: Identifiable { var modifiedAt: Date? var list: TodoList? + // Overdue if deadline is before start of tomorrow and item is not completed var isOverdue: Bool { guard !isCompleted, let deadline else { return false } return deadline < Calendar.current.startOfDay( diff --git a/MindDump/Models/TodoList.swift b/MindDump/Models/TodoList.swift index 69183b7..9ccef9b 100644 --- a/MindDump/Models/TodoList.swift +++ b/MindDump/Models/TodoList.swift @@ -5,6 +5,7 @@ import SwiftData class TodoList: Identifiable { private(set) var id: UUID var name: String + // Cascade delete: removing a list also removes all its items @Relationship(deleteRule: .cascade, inverse: \TodoItem.list) var items: [TodoItem] private(set) var isInbox: Bool diff --git a/MindDump/ViewModels/ListStore.swift b/MindDump/ViewModels/ListStore.swift index f055ef9..d071073 100644 --- a/MindDump/ViewModels/ListStore.swift +++ b/MindDump/ViewModels/ListStore.swift @@ -2,16 +2,20 @@ import Foundation import Observation import SwiftData +// Central ViewModel that manages all lists and items via SwiftData. +// Injected into the view hierarchy as @Environment(ListStore.self). @Observable class ListStore { var lists: [TodoList] = [] private let modelContext: ModelContext + // Convenience accessor — Inbox is guaranteed to exist after init var inboxID: UUID { lists.first { $0.isInbox }!.id } + // All incomplete items across all lists var allOpenItems: [TodoItem] { lists.flatMap { $0.items }.filter { !$0.isCompleted } } @@ -30,6 +34,7 @@ class ListStore { var dueCount: Int { dueItems.count } + // All completed items across all lists, newest first var allCompletedItems: [TodoItem] { lists.flatMap { $0.items } .filter { $0.isCompleted } @@ -49,6 +54,7 @@ class ListStore { fetchLists() } + // Inbox cannot be deleted func deleteList(_ list: TodoList) { guard !list.isInbox else { return } modelContext.delete(list) @@ -120,6 +126,7 @@ class ListStore { deleteItem(item.id, from: listID) } + // Filters open items by urgency/due status and sorts them by the given strategy func filteredOpenItems(for list: TodoList? = nil, urgent: Bool, due: Bool, sort: ListDetailSort) -> [TodoItem] { var items = list?.openItems ?? allOpenItems @@ -137,6 +144,7 @@ class ListStore { case .newest: items.sort { $0.createdAt > $1.createdAt } case .dueDate: + // Items with deadline first (earliest on top), items without deadline at the end items.sort { lhs, rhs in switch (lhs.deadline, rhs.deadline) { case let (l?, r?): l < r @@ -146,6 +154,7 @@ class ListStore { } } case .priority: + // Highest priority first, fallback to createdAt for same priority items.sort { lhs, rhs in let lp = lhs.priority?.rawValue ?? -1 let rp = rhs.priority?.rawValue ?? -1 @@ -157,6 +166,7 @@ class ListStore { return items } + // Moves an item from one list to another and updates its timestamp func moveItem(_ itemID: UUID, from sourceListID: UUID, to targetListID: UUID) { guard sourceListID != targetListID, let sourceList = lists.first(where: { $0.id == sourceListID }), @@ -169,6 +179,7 @@ class ListStore { fetchLists() } + // Creates the Inbox list on first launch if it doesn't exist yet private func ensureInbox() { let descriptor = FetchDescriptor(predicate: #Predicate { $0.isInbox }) let count = (try? modelContext.fetchCount(descriptor)) ?? 0 @@ -179,6 +190,7 @@ class ListStore { } } + // Re-fetches all lists from SwiftData, keeping Inbox at the top private func fetchLists() { let descriptor = FetchDescriptor(sortBy: [SortDescriptor(\.name)]) let fetched = (try? modelContext.fetch(descriptor)) ?? [] @@ -188,6 +200,7 @@ class ListStore { lists = inbox + rest } + // Urgent = high priority OR deadline within 3 days private func isUrgent(_ item: TodoItem) -> Bool { let threeDaysFromNow = Calendar.current.date(byAdding: .day, value: 3, to: Date())! return item.priority == .high || (item.deadline != nil && item.deadline! <= threeDaysFromNow) diff --git a/MindDump/Views/MindDumpButton.swift b/MindDump/Views/MindDumpButton.swift index 2f4de53..e47786d 100644 --- a/MindDump/Views/MindDumpButton.swift +++ b/MindDump/Views/MindDumpButton.swift @@ -1,5 +1,6 @@ import SwiftUI +// Floating action button for quick-dump. Adds to the current list or falls back to Inbox. struct MindDumpButton: View { @Environment(ListStore.self) private var store var activeListID: UUID? diff --git a/MindDump/Views/TodoEditorView.swift b/MindDump/Views/TodoEditorView.swift index 7021462..c4f1133 100644 --- a/MindDump/Views/TodoEditorView.swift +++ b/MindDump/Views/TodoEditorView.swift @@ -1,5 +1,6 @@ import SwiftUI +// Shared editor for creating and editing todos. Used by MindDumpButton and list views. struct TodoEditorView: View { @Environment(ListStore.self) private var store @Environment(\.dismiss) private var dismiss @@ -107,6 +108,7 @@ struct TodoEditorView: View { } } .onAppear { + // When editing, pre-fill fields and show sections that have values if let item { title = item.title @@ -152,6 +154,7 @@ struct TodoEditorView: View { } } + // Saves the entry — creates a new item or updates the existing one private func save() { let trimmed = title.trimmingCharacters(in: .whitespaces) guard !trimmed.isEmpty else { return } diff --git a/MindDumpTests/MindDumpTests.swift b/MindDumpTests/MindDumpTests.swift index 7c31094..efa53bf 100644 --- a/MindDumpTests/MindDumpTests.swift +++ b/MindDumpTests/MindDumpTests.swift @@ -6,14 +6,14 @@ final class TodoItemTests: XCTestCase { // MARK: - isOverdue - /// Past deadline should be overdue + // Past deadline should be overdue func testIsOverdue_deadlineYesterday_returnsTrue() { let yesterday = Calendar.current.date(byAdding: .day, value: -1, to: Date())! let item = TodoItem(title: "Test", deadline: yesterday) XCTAssertTrue(item.isOverdue) } - /// Completed items are never overdue, even with past deadline + // Completed items are never overdue, even with past deadline func testIsOverdue_completedWithPastDeadline_returnsFalse() { let yesterday = Calendar.current.date(byAdding: .day, value: -1, to: Date())! let item = TodoItem(title: "Test", isCompleted: true, deadline: yesterday) @@ -27,7 +27,7 @@ final class TodoListTests: XCTestCase { // MARK: - openItems / completedItems - /// Items are correctly split into open and completed + // Items are correctly split into open and completed func testOpenAndCompletedItems_correctSplit() { let open1 = TodoItem(title: "Open 1") let open2 = TodoItem(title: "Open 2") @@ -40,7 +40,7 @@ final class TodoListTests: XCTestCase { XCTAssertEqual(list.completedItems.first?.title, "Done") } - /// Open items are sorted oldest first + // Open items are sorted oldest first func testOpenItems_sortedByCreatedAtAscending() { let older = TodoItem(title: "Older", createdAt: Date().addingTimeInterval(-100)) let newer = TodoItem(title: "Newer", createdAt: Date()) @@ -50,7 +50,7 @@ final class TodoListTests: XCTestCase { XCTAssertEqual(list.openItems.map(\.title), ["Older", "Newer"]) } - /// Completed items are sorted newest first (by modifiedAt) + // Completed items are sorted newest first (by modifiedAt) func testCompletedItems_sortedByModifiedAtDescending() { let earlier = TodoItem(title: "Earlier", isCompleted: true, modifiedAt: Date().addingTimeInterval(-100)) let later = TodoItem(title: "Later", isCompleted: true, modifiedAt: Date()) @@ -78,7 +78,7 @@ final class ListStoreTests: XCTestCase { // MARK: - addItem / deleteItem - /// Adding an item increases the list count and stores the title + // Adding an item increases the list count and stores the title func testAddItem_increasesCount() { let inboxID = store.inboxID store.addItem(to: inboxID, title: "Buy milk") @@ -87,7 +87,7 @@ final class ListStoreTests: XCTestCase { XCTAssertEqual(store.lists.first!.items.first?.title, "Buy milk") } - /// Deleting an item removes it from the list + // Deleting an item removes it from the list func testDeleteItem_removesItem() { let inboxID = store.inboxID store.addItem(to: inboxID, title: "Temp") @@ -100,7 +100,7 @@ final class ListStoreTests: XCTestCase { // MARK: - toggleItemCompleted - /// Toggling flips isCompleted back and forth + // Toggling flips isCompleted back and forth func testToggleItemCompleted_flipsStatus() { let inboxID = store.inboxID store.addItem(to: inboxID, title: "Task") @@ -117,7 +117,7 @@ final class ListStoreTests: XCTestCase { // MARK: - moveItem - /// Moving an item removes it from the source list, adds it to the target list, and updates modifiedAt + // Moving an item removes it from the source list, adds it to the target list, and updates modifiedAt func testMoveItem_movesAcrossLists() { let inboxID = store.inboxID store.addList(name: "Arbeit")