Android app about managing personal tasks, using react-native. This app primarily targets Android 9 since that's the version my phone uses, but it would be nice to polish & test it for newer versions and maybe even iOS.
The goal is to get personal tasks done in a timely manner. We approach this from two directions:
- Task list: keep track of things to do in a format that encourages actually doing them. Default ordering that emphasizes the most important (or most useful) tasks. Track both recurring tasks and one-off tasks within the same interface. Filtering controls to facilitate finding existing tasks in order to complete them.
- Metrics: track task completion & effort spent over time. This encourages actually using the app and also works a reward mechanism. (Recurring personal tasks - chores - can be frustrating because they're never actually "done". Keeping a record of work completed at least ensures that it is not forgotten.)
Tag {
id: string
name: string
color: string
priority: integer
}
Tags are created while creating Tasks, but they should be stored separately since we will need to be able to fetch all tags for filtering + autocomplete.
Suggestions for tags:
home
- housework, both recurring tasks like chores and one-off tasks like "fix wobbly chair leg"outdoors
- tasks which take place outside. useful for filtering since completion depends on weather and time of dayhealth
- exercise, taking vitamins, etcshopping
- tasks that involve buying stuff- people or pets (
parents
,dog
, etc)
Customizing the priority
of a tag is a nice-to-have. This would factor into task urgency in a similar way to the priority of the task itself.
(See src/types/task.ts)
Task {
id
settings: TaskSettings
createdAt: integer (timestamp)
tagIds: string[]
completionIds: string[]
runningPoints: integer
}
TaskSettings {
name: string
type: 'recurring' | 'once' | 'bucket'
points: integer
priority: integer from -2 to 2 (0 is neutral)
interval?: Interval
deadline?: DateTime
deadlineWarning?: Interval
scheduled?: DateTime
notes?: string
}
Interval {
frequency: enum (day, week, month, year)
count: integer
}
DateTime {
date: [int, int, int] // year, month, day
time?: [int, int] // hour, minute
}
Constraints:
points
must be a positive integerrecurrence
is required iftype
isrecurring
scheduled
anddeadline
are mutually exclusive, if one is set the other must be null- if
type
isrecurring
then one ofscheduled
ordeadline
must be set - if
type
isonce
then both are optional - it
type
isbucket
then both must be null deadlineWarning
is required ifdeadline
is set
A bucket is a task that isn't ever really "finished", but is just something you dump points into on a regular basis - eg, taking out the trash, or tidying up. Bucket tasks repurpose the points
and interval
fields to indicate the "size" of the bucket in points-per-interval (eg, 5 points every 2 weeks).
Recurring tasks require an interval
. This is essentially a duration (frequency
denotes days/weeks/months and count
determines how many), and the interval of a task represents how often the task needs to be done.
For our use case it should be sufficient to reset the interval whenever the task is completed, meaning we mutate scheduled
or deadline
on the task record when that happens.
Generally recurring tasks will have scheduled dates, but theoretically they could have deadlines (eg, pay a bill that can't be automated), so we should also support that case.
For scheduling tasks, time of day is mostly irrelevant so it should be optional. Thus instead of saving a regular timestamp, we save times as objects with a required date property and an optional time property. When converting these to timestamps for date math, we use a default time (eventually configurable, set to 12pm for now) for values which don't have a time set.
Taking a page from org-mode, tasks can (optionally) have a scheduled time or a deadline.
A scheduled date is simply the date at which it becomes time to perform the task (or at least start thinking about it). The urgency of the task increases with time elapsed since the scheduled date. This design assumes that we don't need to worry about tasks before their scheduled date.
A deadline indicates that the task should be completed before the date in question - thus we should surface tasks with deadlines well before the deadline. As the deadline for a task approaches, the task grows more urgent (as a function of its point value) and therefore should be displayed more prominently (ie higher up) in the task list when using "smart" sorting.
Completion: {
taskId: string
date: DateTime
points: number
isPartial: boolean
category: string
}
Completions are many-to-one with Tasks. The points
value of a completion can be different from the point value of the parent task.
When creating a completion, inputs for points
and date
should default to the point value of the task and the current date respectively, but should be configurable in the UI.
A completion typically but not necessarily indicates that we should mark the task as "completed", meaning we reset its scheduled time or deadline if it is recurring, and represent it in the task list as having been completed.
Category {
id: string
name: string
}
(Note: this is low priority)
Categories are one-to-many with completions (a completion can have a single category). These allow us to bucket completions for analytics purposes. A competion category can be anything, but the primary use case is to represent the person who completed it: eg, if a housekeeper has cleaned the bathroom, we can mark the "clean bathroom" task as completed with the housekeeper
category, so that we can filter those completions out when we view analytics for work done ourselves.
- theme
- default deadline warning
- default number of points (or "none" to start blank)
- tweak urgency scaling
- tag manager
- completion categories
We need a datastore that is really flexible about querying, since we should be able to build clauses dynamically based on filters.
Eg:
const getSortedAndFilteredTasks = (
state: State,
...filters: ((task: Task) => boolean)[]
) => state.tasks.filter(compose(...filters))
where filters
would be something like:
const isActive = task =>
task.scheduled < new Date() ||
sub(task.deadline, deadlineWarning) < new Date()
const isNotCompleted = task =>
(!task.isRecurring && !task.lastCompletedAt) ||
(task.isRecurring && isActive(task))
By default the task list should filter out completed tasks.
One-offs are completed if there exists any completion record for them with isPartial = false
. A recurring task is considered completed (temporarily) when scheduled
is in the future, or when the current date is before deadline
minus deadlineWarning
.
Other filter options:
- text search (probably just task name)
- tags: include and exclude
- priority
- scheduled
- deadline
- recurring or one-off
- completed or not completed
- point value range - a double-sided slider would be nice here
The urgency of a task is a function of its priority and the time between the current time and the scheduled time or deadline.
Note that some tasks will not (and should not) have scheduled times or deadlines. These tasks should probably be handled as though they are of a lower priority than scheduled/deadline tasks.
How do we weigh time past scheduled date or time until deadline against priority?
Task size should factor into the time until deadline weight - larger tasks become more urgent near their deadline than smaller tasks because it is assumed that they will take longer to finish.
The "utility" of a task is a function of its urgency and the inverse of its point value (smaller tasks lead to higher utility).
- Priority
- Scheduled/deadline
- Age?
- Alphabetical
- A line graph of time on the x axis and points completed on the y axis
- A histogram of times completed (like activity on github)
Point totals can apply to one or more tasks. The default metrics view should show point totals for all tasks; the detail page for an individual task should link to point totals for that task.
Can filter and bucket by task properties. Grouping would work for any exclusive category (eg, recurring vs one-off since a task cannot be both, or priority - not tags since tasks can have multiple).
Average points completed per day over each week. Intervals can be customized, eg points per week over the last month/year, etc.
Can represent with a line graph or histogram just like with point totals, and can be filtered/bucketed along the same lines.
Pie charts of all tasks broken down along various axes.
We have the data to compare the average completion interval (time between completions) against the recurrence interval for a task. Not sure how to represent this though.