fix: add inline comments to models, viewmodel and views for better readable code
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<TodoList>(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<TodoList>(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)
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -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 }
|
||||
|
||||
Reference in New Issue
Block a user