figbert.com-website

[ACTIVE] the website and home of figbert on the clearnet
git clone git://git.figbert.com/figbert.com-website.git
Log | Files | Refs | README | LICENSE

commit d61ecc1d12125fbd778276b2b135c052a3dcf6e1
parent 2e666f9a73aa9620135c0d2d533879e6d17bff36
Author: FIGBERT <figbert@figbert.com>
Date:   Wed, 19 Aug 2020 22:48:34 -0700

:speech_balloon: Update txtodo project writeup

Diffstat:
Dcontent/projects/txtodo/core-data-structs.png | 0
Mcontent/projects/txtodo/index.md | 108+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
Dcontent/projects/txtodo/manage-task-deletion.png | 0
Dcontent/projects/txtodo/tap-modifiers-order.png | 0
4 files changed, 86 insertions(+), 22 deletions(-)

diff --git a/content/projects/txtodo/core-data-structs.png b/content/projects/txtodo/core-data-structs.png Binary files differ. diff --git a/content/projects/txtodo/index.md b/content/projects/txtodo/index.md @@ -2,14 +2,15 @@ title = "txtodo" description = "" date = 2020-05-20 +updated = 2020-08-19 weight = 0 +++ [**txtodo**][txtodo] is a minimalist open-source todo list app inspired by Jeff Huang's [*One File to Rule Them All*][one-file-to-rule-them-all]. It lists your immediate, short-term tasks to help you get things done without overthinking it. With txtodo, you list all your tasks for the day in the morning, and throughout the day you check off tasks as you complete them. Then, at midnight, all tasks are discarded so you can start fresh tomorrow – you can also create up to three long-term tasks that "float" with you from -day-to-day. Did I mention that it's completely open-source and built with SwiftUI? You can download txtodo from the app store [here][txtodo-store] and view the source code -[here][txtodo-src]. +day-to-day. You can also delay tasks to the next day, but only once – if you don't complete the task the next day, txtodo moves on. Did I mention that it's completely +open-source and built with SwiftUI? You can download txtodo from the app store [here][txtodo-store] and view the source code [here][txtodo-src]. <!-- more --> @@ -31,32 +32,98 @@ I've personally found this to be very useful, especially in the COVID-19 quarant pushing me to finish schoolwork, code everyday, and keep developing new projects. ### Code -txtodo is programmed 100% in Swift, with no third-party dependencies. It's also written with the most recent frameworks, [SwiftUI][swiftui] and [Combine][combine] (though as -SwiftUI is still new, I did have to write some wrappers from UIKit). The tasks are stored using the two core data models below: +txtodo is programmed 100% in Swift, with no third-party dependencies. It's also written with the most recent frameworks, [SwiftUI][swiftui] and [Combine][combine] (though +initially this required some UIKit wrappers, though it now uses the WWDC2020 [App and Scene][app-scene-architecture] SwiftUI structure). The tasks are stored using the core data +`Task` model below: -![core data models][core-data-models] +```swift +extension Task { + @nonobjc public class func fetchRequest() -> NSFetchRequest<Task> { + return NSFetchRequest<Task>(entityName: "Task") + } + @NSManaged public var completed: Bool // whether or not the task is done + @NSManaged public var daily: Bool // whether the task is daily or floating + @NSManaged public var date: Date // if it's a daily task, the date it was created. otherwise, the date it was most recently marked as completed + @NSManaged public var id: UUID // a unique task ID + @NSManaged public var name: String // the name/main text of the task + @NSManaged public var notes: [String] // an array of the task's notes + @NSManaged public var priority: Int16 // the task priority (1, 2, or 3) + @NSManaged public var hasBeenDelayed: Bool // if it's a daily task, this bool shows whether or not the task has been delayed +} +``` Once a task is initialized, it's stored in an `NSPersistentCloudKitContainer` until its deleted, and displayed on the homescreen by a `ForEach` loop iterating over a `FetchRequest`. (For more on Core Data and SwiftUI, I would point you to [this excellent tutorial][core-data-tutorial].) This `ForEach` loop is the only place where we can control all the tasks in the list at once, and so it's here that we manage the tasks. Once we initialize each task's view, we add an `onAppear()` modifier to check if the task -should be deleted. If we're modifying a `dailyTaskView`, we check if the task's `creationDate` is equal to the day's date – if the dates match the task is displayed, and if they -don't the task is deleted from storage. If we're modifying a `floatingTaskView`, the process is a little more complicated. The task should only be deleted if it is completed and -the completion is a day or more old. This process looks something like this: +should be deleted. If we're modifying a daily task, we check if the task's `date` precedes the current day and, if true, deletes the task from storage. If we're modifying a +floating task, the process is a little more complicated. The task should only be deleted if it is completed and the completion is a day or more old. This process looks something +like this: -![task deletion modifiers][task-deletion-modifiers] +```swift +let currentDay = Calendar.current.component(.day, from: Date.init()) -Another interesting thing about SwiftUI: the order of certain view modifiers affects the behavior of the view they're modifying. This is particularly apparent when dealing with -different combination of tap gestures. In my case, I want my task views to have single-tap, double-tap, and long press interactions. To get these interactions to work together -without conflicting, they have to be ordered like so: +TaskView() // A floating task + .onAppear(perform: { + if ( + task.completed && + Calendar.current.component(.day, from: task.date) < self.currentDay + ) { + // delete the task + } + }) +TaskView() // A daily task + .onAppear(perform: { + if Calendar.current.component(.day, from: task.date) < self.currentDay { + // delete the task + } + }) +``` -![order of tap modifiers][ordering-modifiers] +Tasks are sorted on the homescreen by two `FetchRequest`s. To split Core Data objects of the same type into two different variables, we use `NSPredicate` to select what is added +to each array, and then we sort the array using chained `NSSortDescriptor`s. The current variables are defined as such: -I plan on writing more about that in a blog post about the quirks and bugs of SwiftUI. +```swift +@FetchRequest( + entity: Task.entity(), + sortDescriptors: [ + NSSortDescriptor(keyPath: \Task.completed, ascending: true), + NSSortDescriptor(keyPath: \Task.priority, ascending: false), + NSSortDescriptor(keyPath: \Task.name, ascending: true) + ], + predicate: NSPredicate(format: "daily == %d", false) +) var floatingTasks: FetchedResults<Task> +@FetchRequest( + entity: Task.entity(), + sortDescriptors: [ + NSSortDescriptor(keyPath: \Task.completed, ascending: true), + NSSortDescriptor(keyPath: \Task.priority, ascending: false), + NSSortDescriptor(keyPath: \Task.name, ascending: true) + ], + predicate: NSPredicate( + format: "daily == %d AND date < %@", + argumentArray: [ + true, + Calendar.current.startOfDay( + for: Calendar.current.date( + byAdding: .day, + value: 1, + to: Date() + )! + ) + ] + ) +) var dailyTasks: FetchedResults<Task> +``` -## # Website -The txtodo website, [txtodo.app][txtodo], is programmed with [Svelte][svelte] – not [Sapper][sapper], it's a single static page and doesn't need all that extra overhead – and -[TailwindCSS][tailwind]. The combination of these two frameworks has become my all time favorite – together, they give the comforting feeling of programming in pure HTML+JS+CSS, -with the ease and enhaced capability of the most modern competing frameworks. You can [view the code for the site here][site-src]. +Floating tasks are selected by checking if `daily == false`, and then sorted first by completion status (completed tasks are below tasks yet to be marked as done), then by +priority, and then alphabetized. Daily tasks are more complicated: the predicate first filters out all floating tasks, before filtering out all the daily tasks **that have dates +less than tomorrow at midnight**. What this ends up doing is returning all daily tasks created today and any time before, but lets tasks with creation dates in the future – +daily tasks that have been delayed – sit in the database until their date arrives. + +## Website +The txtodo website, [txtodo.app][txtodo], is programmed with [Svelte][svelte] and [TailwindCSS][tailwind]. The combination of these two frameworks has become my all time +favorite – together, they give the comforting feeling of programming in pure HTML+JS+CSS, with the ease and enhaced capability of the most modern competing frameworks. You can +[view the code for the site here][site-src]. [txtodo]: https://txtodo.app/ [one-file-to-rule-them-all]: https://jeffhuang.com/productivity_text_file/ @@ -71,12 +138,9 @@ with the ease and enhaced capability of the most modern competing frameworks. Yo [featurism]: http://www.catb.org/jargon/html/C/creeping-featurism.html [swiftui]: https://developer.apple.com/xcode/swiftui/ [combine]: https://developer.apple.com/documentation/combine -[core-data-models]: core-data-structs.png +[app-scene-architecture]: https://developer.apple.com/documentation/swiftui/app-structure-and-behavior [core-data-tutorial]: https://www.hackingwithswift.com/quick-start/swiftui/introduction-to-using-core-data-with-swiftui -[task-deletion-modifiers]: manage-task-deletion.png -[ordering-modifiers]: tap-modifiers-order.png [svelte]: https://svelte.dev/ -[sapper]: https://sapper.svelte.dev/ [tailwind]: https://tailwindcss.com/ [site-src]: https://github.com/FIGBERT/txtodo.app diff --git a/content/projects/txtodo/manage-task-deletion.png b/content/projects/txtodo/manage-task-deletion.png Binary files differ. diff --git a/content/projects/txtodo/tap-modifiers-order.png b/content/projects/txtodo/tap-modifiers-order.png Binary files differ.