TaskView.swift 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. //
  2. // TaskView.swift
  3. // Todos
  4. //
  5. // Created by Sam Jaffe on 2/28/26.
  6. //
  7. import SwiftUI
  8. import SwiftData
  9. struct TaskView: View {
  10. @Environment(\.modelContext) private var modelContext
  11. @AppStorage(UserDefaultsKeys.Category) var allGroups = CodableArray<Category>()
  12. @Binding var task: Task
  13. @State private var hideTags: Bool = false
  14. @State private var hideNotes: Bool = false
  15. @State private var empty = Category()
  16. @Binding var isMoveMode: Bool
  17. @FocusState private var isFocused: Bool
  18. var body: some View {
  19. VStack {
  20. HStack {
  21. if let grp = $allGroups.first(where: { $0.name.wrappedValue == task.category }) {
  22. ColorPicker("", selection: grp.color).disabled(true).scaledToFit()
  23. }
  24. Image(systemName: task.status.label)
  25. .frame(width: 20)
  26. .padding(.trailing, -10)
  27. Picker("", selection: $task.status) {
  28. ForEach(Status.allCases) { unit in
  29. Text(String(describing: unit))
  30. }
  31. }
  32. .fixedSize(horizontal: true, vertical: false)
  33. .onChange(of: task.status) {
  34. if task.status.isStrong {
  35. task.subtasks
  36. .filter({ !$0.status.isStrong })
  37. .forEach({ subtask in subtask.status = task.status })
  38. }
  39. }
  40. TextField("Task Name", text: $task.name)
  41. .focused($isFocused)
  42. if isMoveMode {
  43. Button {
  44. deleteItem(item: task)
  45. } label: {
  46. Image(systemName: "trash")
  47. }.help("Delete Task '\(task.name)'")
  48. } else {
  49. Button(action: addItem) {
  50. Image(systemName: "plus")
  51. .help("Add a Subtask")
  52. }
  53. }
  54. }
  55. if isFocused || !(hideTags || task.tags.isEmpty) {
  56. HStack {
  57. TagBarView(task: $task)
  58. .font(.footnote)
  59. .padding(.leading, 30)
  60. VisibilityTapper(hideToggle: $hideTags)
  61. if isFocused {
  62. Picker("", selection: $task.category) {
  63. Text(empty.name).tag("")
  64. ForEach(allGroups) { group in
  65. Text(group.name)
  66. }
  67. }
  68. .fixedSize(horizontal: true, vertical: false)
  69. }
  70. }.focused($isFocused)
  71. }
  72. if isFocused || !(hideNotes || task.notes.isEmpty) {
  73. HStack {
  74. TextField("Notes", text: $task.notes, axis: .vertical)
  75. .font(.footnote)
  76. .padding(.leading, 30)
  77. VisibilityTapper(hideToggle: $hideNotes)
  78. }.focused($isFocused)
  79. }
  80. VStack {
  81. ForEach($task.subtasks, id: \.self) { subtask in
  82. HStack {
  83. SubTaskView(task: subtask)
  84. .padding(.leading, 5)
  85. if isMoveMode {
  86. Button {
  87. deleteItem(item: subtask.wrappedValue, fromTask: task)
  88. } label: {
  89. Image(systemName: "trash")
  90. }.help("Delete Subtask '\(subtask.name.wrappedValue)'")
  91. }
  92. }
  93. }
  94. }
  95. }
  96. }
  97. private func addItem() {
  98. withAnimation {
  99. let newSubtask = SubTask(name: "Subtask", parent: task)
  100. modelContext.insert(newSubtask)
  101. task.subtasks.append(newSubtask)
  102. }
  103. }
  104. private func deleteItem(item: Task) {
  105. withAnimation {
  106. if let fromProject = item.project {
  107. fromProject.tasks.removeAll(where: { $0.id == item.id })
  108. modelContext.delete(item)
  109. }
  110. }
  111. }
  112. private func deleteItem(item: SubTask, fromTask: Task) {
  113. withAnimation {
  114. fromTask.subtasks.removeAll(where: { $0.id == item.id })
  115. modelContext.delete(item)
  116. }
  117. }
  118. }
  119. #Preview {
  120. @Previewable @State var isMoveMode = false
  121. @Previewable @State var task = Task(name: "New Task")
  122. TaskView(task: $task, isMoveMode: $isMoveMode)
  123. .frame(minHeight: 100) // Preview does not resize window properly
  124. }