krz/libre-tasks

A free and private task manager for iOS.

clone: git clone https://gitbay.org/krz/libre-tasks.git

cf13000e6fb6af629d7659dd3e99ccb9496e6462

unsigned

author: Christian Cleberg <hello@cleberg.net> · 2024-01-04T20:26:57Z

feat: improve new task form and swipe actions
 LibreTasks/ContentView.swift                       | 142 ++++++++++++++++-----
 .../DataModel.xcdatamodel/contents                 |  12 ++
 LibreTasks/DataStore.swift                         |   8 +-
 3 files changed, 131 insertions(+), 31 deletions(-)

diff --git a/LibreTasks/ContentView.swift b/LibreTasks/ContentView.swift
index 226db4d..2ea9013 100644
--- a/LibreTasks/ContentView.swift
+++ b/LibreTasks/ContentView.swift
@@ -9,10 +9,26 @@ import Combine
 
 struct ContentView: View {
     @ObservedObject var taskStore = TaskDataStore()
-    @State var newTask : String = ""
+    @State private var newTask : String = ""
+    @State private var taskPriority : String = ""
     @State private var query = ""
-    @State var newTaskDate: Date = Date()
+    @State private var newTaskDate: Date = Date()
     @State private var showingPopover = false
+    @State private var recurringReminder = false
+    @State private var recurrenceInteger : Int = 1
+    @State private var showAlert: Bool = false
+    
+    enum Priority: String, CaseIterable, Identifiable {
+        case A, B, C
+        var id: Self { self }
+    }
+    @State private var selectedPriority: Priority = .A
+    
+    enum Recurrence: String, CaseIterable, Identifiable {
+        case hours, days, weeks, months
+        var id: Self { self }
+    }
+    @State private var selectedRecurrence: Recurrence = .hours
     
     var filteredTasks: [Task] {
         if query.isEmpty {
@@ -25,13 +41,21 @@ struct ContentView: View {
     }
     
     func addNewTask() {
-        taskStore.tasks.append(Task(
-            id: String(taskStore.tasks.count + 1),
-            taskItem: newTask,
-            taskDate: newTaskDate
-        ))
-        self.newTask = ""
-        self.showingPopover = false
+        if newTask.isEmpty {
+            showAlert = true
+        } else {
+            taskStore.tasks.append(Task(
+                id: (taskStore.tasks.count + 1),
+                taskItem: newTask,
+                taskDate: newTaskDate,
+                taskPriority: selectedPriority.rawValue,
+                taskRecurrence: recurringReminder,
+                taskRecurrenceInt: recurrenceInteger,
+                taskRecurrencePeriod: selectedRecurrence.rawValue
+            ))
+            self.newTask = ""
+            self.showingPopover = false
+        }
     }
 
     var body: some View {
@@ -39,25 +63,51 @@ struct ContentView: View {
             VStack {
                 List {
                     ForEach(filteredTasks) { task in
-                        VStack(alignment: .leading) {
-                            Text(task.taskItem)
-                            if (Date() > task.taskDate) {
-                                HStack {
-                                    Text(task.taskDate, style: .date)
-                                        .foregroundColor(.red)
-                                        .font(.caption)
-                                    Text(task.taskDate, style: .time)
-                                        .foregroundColor(.red)
-                                        .font(.caption)
-                                }
-                            } else {
-                                HStack {
-                                    Text(task.taskDate, style: .date)
-                                        .font(.caption)
-                                    Text(task.taskDate, style: .time)
-                                        .font(.caption)
+                        HStack {
+                            VStack(alignment: .leading) {
+                                Text(task.taskItem)
+                                if (Date() > task.taskDate) {
+                                    HStack {
+                                        Text(task.taskDate, style: .date)
+                                            .foregroundColor(.red)
+                                            .font(.caption)
+                                        Text(task.taskDate, style: .time)
+                                            .foregroundColor(.red)
+                                            .font(.caption)
+                                        if (task.taskRecurrence == true) {
+                                            (Text("[+") + Text(String(task.taskRecurrenceInt)) + Text(" ") + Text(task.taskRecurrencePeriod) + Text("]"))
+                                                .foregroundColor(.red)
+                                                .font(.caption)
+                                        }
+                                    }
+                                } else {
+                                    HStack {
+                                        Text(task.taskDate, style: .date)
+                                            .font(.caption)
+                                        Text(task.taskDate, style: .time)
+                                            .font(.caption)
+                                        if (task.taskRecurrence == true) {
+                                            (Text("[+") + Text(String(task.taskRecurrenceInt)) + Text(" ") + Text(task.taskRecurrencePeriod) + Text("]"))
+                                                .font(.caption)
+                                        }
+                                    }
                                 }
                             }
+                            Spacer()
+                            (Text("[#") + Text(task.taskPriority) + Text("]"))
+                        }
+                        .swipeActions(edge: .trailing) {
+                            Button(role: .destructive) {
+                                print("\(task.taskItem) is being deleted.")
+                            } label: {
+                                Label("Delete", systemImage: "trash")
+                            }
+                            Button() {
+                                print("\(task.taskItem) is being edited.")
+                                // TODO: Add method to edit message in the DataStore
+                            } label: {
+                                Label("Edit", systemImage: "pencil")
+                            }
                         }
                     }
                     .onDelete(perform: self.deleteTask)
@@ -85,12 +135,44 @@ struct ContentView: View {
                                 })
                             }
                             .padding()
-                            HStack {
-                                VStack {
-                                    TextField("Add Task: ", text: self.$newTask)
-                                    DatePicker("Scheduled: ", selection: $newTaskDate)
+                            Form {
+                                Section(header: Text("Task Details")) {
+                                    TextField("Task Title", text: self.$newTask)
+                                        .onSubmit {
+                                            if newTask.isEmpty {
+                                                showAlert = true
+                                            }
+                                        }
+                                        .alert(isPresented: $showAlert) {
+                                            Alert(title: Text("Required Field"),
+                                                  message: Text("You must enter a task title"),
+                                                  dismissButton: .default(Text("OK")))
+                                        }
+                                    Picker("Priority:", selection: $selectedPriority) {
+                                        Text("A").tag(Priority.A)
+                                        Text("B").tag(Priority.B)
+                                        Text("C").tag(Priority.C)
+                                    }
+                                    DatePicker("Scheduled:", selection: $newTaskDate)
                                         .datePickerStyle(CompactDatePickerStyle())
                                 }
+                                Section(header: Text("Repeating")) {
+                                    Toggle("Repeating", isOn: $recurringReminder)
+                                    if (recurringReminder == true) {
+                                        HStack {
+                                            TextField("", value: $recurrenceInteger, formatter: NumberFormatter())
+                                            Stepper(value: $recurrenceInteger, in: 1...100) {
+                                                EmptyView()
+                                            }
+                                            Picker("", selection: $selectedRecurrence) {
+                                                Text("Hours").tag(Recurrence.hours)
+                                                Text("Days").tag(Recurrence.days)
+                                                Text("Weeks").tag(Recurrence.weeks)
+                                                Text("Months").tag(Recurrence.months)
+                                            }
+                                        }
+                                    }
+                                }
                             }
                             .padding()
                             Spacer()
diff --git a/LibreTasks/DataModel.xcdatamodeld/DataModel.xcdatamodel/contents b/LibreTasks/DataModel.xcdatamodeld/DataModel.xcdatamodel/contents
new file mode 100644
index 0000000..5287d8e
--- /dev/null
+++ b/LibreTasks/DataModel.xcdatamodeld/DataModel.xcdatamodel/contents
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="22522" systemVersion="23C71" minimumToolsVersion="Automatic" sourceLanguage="Swift" usedWithSwiftData="YES" userDefinedModelVersionIdentifier="">
+    <entity name="Tasks" representedClassName="Tasks" syncable="YES" codeGenerationType="class">
+        <attribute name="id" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
+        <attribute name="taskDate" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
+        <attribute name="taskItem" optional="YES" attributeType="String"/>
+        <attribute name="taskPriority" optional="YES" attributeType="String"/>
+        <attribute name="taskRecurrence" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
+        <attribute name="taskRecurrenceInt" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
+        <attribute name="taskRecurrencePeriod" optional="YES" attributeType="String"/>
+    </entity>
+</model>
\ No newline at end of file
diff --git a/LibreTasks/DataStore.swift b/LibreTasks/DataStore.swift
index 1628979..76733de 100644
--- a/LibreTasks/DataStore.swift
+++ b/LibreTasks/DataStore.swift
@@ -8,10 +8,16 @@ import Foundation
 import SwiftUI
 import Combine
 
+// TODO: Persist data storage across sessions
+
 struct Task: Identifiable {
-    var id = String()
+    var id = Int()
     var taskItem = String()
     var taskDate = Date()
+    var taskPriority = String()
+    var taskRecurrence = Bool()
+    var taskRecurrenceInt = Int()
+    var taskRecurrencePeriod = String()
 }
 
 class TaskDataStore: ObservableObject {