krz/libre-tasks

A free and private task manager for iOS. ios privacy swift tasks

Commit cf13000e6f

cf13000e6fb6af629d7659dd3e99ccb9496e6462

parent: 72028eb2a2

Unsigned

cmc <hello@cleberg.net> · 2024-01-04 20:26 UTC

feat: improve new task form and swipe actions

Layout: unified · split

LibreTasks/ContentView.swift +112 −30
@@ -9,10 +9,26 @@ import Combine
9 9
10struct ContentView: View { 10struct ContentView: View {
11 @ObservedObject var taskStore = TaskDataStore() 11 @ObservedObject var taskStore = TaskDataStore()
12 @State var newTask : String = "" 12 @State private var newTask : String = ""
13 @State private var taskPriority : String = ""
13 @State private var query = "" 14 @State private var query = ""
14 @State var newTaskDate: Date = Date() 15 @State private var newTaskDate: Date = Date()
15 @State private var showingPopover = false 16 @State private var showingPopover = false
17 @State private var recurringReminder = false
18 @State private var recurrenceInteger : Int = 1
19 @State private var showAlert: Bool = false
20
21 enum Priority: String, CaseIterable, Identifiable {
22 case A, B, C
23 var id: Self { self }
24 }
25 @State private var selectedPriority: Priority = .A
26
27 enum Recurrence: String, CaseIterable, Identifiable {
28 case hours, days, weeks, months
29 var id: Self { self }
30 }
31 @State private var selectedRecurrence: Recurrence = .hours
16 32
17 var filteredTasks: [Task] { 33 var filteredTasks: [Task] {
18 if query.isEmpty { 34 if query.isEmpty {
@@ -25,13 +41,21 @@ struct ContentView: View {
25 } 41 }
26 42
27 func addNewTask() { 43 func addNewTask() {
28 taskStore.tasks.append(Task( 44 if newTask.isEmpty {
29 id: String(taskStore.tasks.count + 1), 45 showAlert = true
30 taskItem: newTask, 46 } else {
31 taskDate: newTaskDate 47 taskStore.tasks.append(Task(
32 )) 48 id: (taskStore.tasks.count + 1),
33 self.newTask = "" 49 taskItem: newTask,
34 self.showingPopover = false 50 taskDate: newTaskDate,
51 taskPriority: selectedPriority.rawValue,
52 taskRecurrence: recurringReminder,
53 taskRecurrenceInt: recurrenceInteger,
54 taskRecurrencePeriod: selectedRecurrence.rawValue
55 ))
56 self.newTask = ""
57 self.showingPopover = false
58 }
35 } 59 }
36 60
37 var body: some View { 61 var body: some View {
@@ -39,25 +63,51 @@ struct ContentView: View {
39 VStack { 63 VStack {
40 List { 64 List {
41 ForEach(filteredTasks) { task in 65 ForEach(filteredTasks) { task in
42 VStack(alignment: .leading) { 66 HStack {
43 Text(task.taskItem) 67 VStack(alignment: .leading) {
44 if (Date() > task.taskDate) { 68 Text(task.taskItem)
45 HStack { 69 if (Date() > task.taskDate) {
46 Text(task.taskDate, style: .date) 70 HStack {
47 .foregroundColor(.red) 71 Text(task.taskDate, style: .date)
48 .font(.caption) 72 .foregroundColor(.red)
49 Text(task.taskDate, style: .time) 73 .font(.caption)
50 .foregroundColor(.red) 74 Text(task.taskDate, style: .time)
51 .font(.caption) 75 .foregroundColor(.red)
52 } 76 .font(.caption)
53 } else { 77 if (task.taskRecurrence == true) {
54 HStack { 78 (Text("[+") + Text(String(task.taskRecurrenceInt)) + Text(" ") + Text(task.taskRecurrencePeriod) + Text("]"))
55 Text(task.taskDate, style: .date) 79 .foregroundColor(.red)
56 .font(.caption) 80 .font(.caption)
57 Text(task.taskDate, style: .time) 81 }
58 .font(.caption) 82 }
83 } else {
84 HStack {
85 Text(task.taskDate, style: .date)
86 .font(.caption)
87 Text(task.taskDate, style: .time)
88 .font(.caption)
89 if (task.taskRecurrence == true) {
90 (Text("[+") + Text(String(task.taskRecurrenceInt)) + Text(" ") + Text(task.taskRecurrencePeriod) + Text("]"))
91 .font(.caption)
92 }
93 }
59 } 94 }
60 } 95 }
96 Spacer()
97 (Text("[#") + Text(task.taskPriority) + Text("]"))
98 }
99 .swipeActions(edge: .trailing) {
100 Button(role: .destructive) {
101 print("\(task.taskItem) is being deleted.")
102 } label: {
103 Label("Delete", systemImage: "trash")
104 }
105 Button() {
106 print("\(task.taskItem) is being edited.")
107 // TODO: Add method to edit message in the DataStore
108 } label: {
109 Label("Edit", systemImage: "pencil")
110 }
61 } 111 }
62 } 112 }
63 .onDelete(perform: self.deleteTask) 113 .onDelete(perform: self.deleteTask)
@@ -85,12 +135,44 @@ struct ContentView: View {
85 }) 135 })
86 } 136 }
87 .padding() 137 .padding()
88 HStack { 138 Form {
89 VStack { 139 Section(header: Text("Task Details")) {
90 TextField("Add Task: ", text: self.$newTask) 140 TextField("Task Title", text: self.$newTask)
91 DatePicker("Scheduled: ", selection: $newTaskDate) 141 .onSubmit {
142 if newTask.isEmpty {
143 showAlert = true
144 }
145 }
146 .alert(isPresented: $showAlert) {
147 Alert(title: Text("Required Field"),
148 message: Text("You must enter a task title"),
149 dismissButton: .default(Text("OK")))
150 }
151 Picker("Priority:", selection: $selectedPriority) {
152 Text("A").tag(Priority.A)
153 Text("B").tag(Priority.B)
154 Text("C").tag(Priority.C)
155 }
156 DatePicker("Scheduled:", selection: $newTaskDate)
92 .datePickerStyle(CompactDatePickerStyle()) 157 .datePickerStyle(CompactDatePickerStyle())
93 } 158 }
159 Section(header: Text("Repeating")) {
160 Toggle("Repeating", isOn: $recurringReminder)
161 if (recurringReminder == true) {
162 HStack {
163 TextField("", value: $recurrenceInteger, formatter: NumberFormatter())
164 Stepper(value: $recurrenceInteger, in: 1...100) {
165 EmptyView()
166 }
167 Picker("", selection: $selectedRecurrence) {
168 Text("Hours").tag(Recurrence.hours)
169 Text("Days").tag(Recurrence.days)
170 Text("Weeks").tag(Recurrence.weeks)
171 Text("Months").tag(Recurrence.months)
172 }
173 }
174 }
175 }
94 } 176 }
95 .padding() 177 .padding()
96 Spacer() 178 Spacer()
LibreTasks/DataModel.xcdatamodeld/DataModel.xcdatamodel/contents added +12
@@ -0,0 +1,12 @@
1<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="22522" systemVersion="23C71" minimumToolsVersion="Automatic" sourceLanguage="Swift" usedWithSwiftData="YES" userDefinedModelVersionIdentifier="">
3 <entity name="Tasks" representedClassName="Tasks" syncable="YES" codeGenerationType="class">
4 <attribute name="id" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
5 <attribute name="taskDate" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
6 <attribute name="taskItem" optional="YES" attributeType="String"/>
7 <attribute name="taskPriority" optional="YES" attributeType="String"/>
8 <attribute name="taskRecurrence" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
9 <attribute name="taskRecurrenceInt" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
10 <attribute name="taskRecurrencePeriod" optional="YES" attributeType="String"/>
11 </entity>
12</model>\ No newline at end of file
LibreTasks/DataStore.swift +7 −1
@@ -8,10 +8,16 @@ import Foundation
8import SwiftUI 8import SwiftUI
9import Combine 9import Combine
10 10
11// TODO: Persist data storage across sessions
12
11struct Task: Identifiable { 13struct Task: Identifiable {
12 var id = String() 14 var id = Int()
13 var taskItem = String() 15 var taskItem = String()
14 var taskDate = Date() 16 var taskDate = Date()
17 var taskPriority = String()
18 var taskRecurrence = Bool()
19 var taskRecurrenceInt = Int()
20 var taskRecurrencePeriod = String()
15} 21}
16 22
17class TaskDataStore: ObservableObject { 23class TaskDataStore: ObservableObject {