Begin working on Habit Screens project

Created a small MVP for digitizing my weekly habits. Much more to come.

Lots of things happening:

- Copied the boilerplate to get started
- Added a brief project-level README
- Outlined my ambitions in design.md

See README and design.md for more context on this project.
This commit is contained in:
William Carroll 2020-10-10 17:04:24 +01:00
parent 02ce74eada
commit 9d331f3077
12 changed files with 414 additions and 0 deletions

View file

@ -0,0 +1,12 @@
# Habit Screens
Problem: I would like to increase the rate at which I complete my daily, weekly,
monthly, yearly habits.
Solution: Habit Screens are mounted in strategic locations throughout my
apartment. Each Habit Screen displays the habits that I should complete that
day, and I can tap each item to mark it as complete. I will encounter the Habit
Screens in my bedroom, kitchen, and bathroom, so I will have adequate "cues" to
focus my attention. By marking each item as complete and tracking the results
over time, I will have more incentive to maintain my consistency
(i.e. "reward").

View file

@ -0,0 +1,2 @@
source_up
use_nix

View file

@ -0,0 +1,3 @@
/elm-stuff
/Main.min.js
/output.css

View file

@ -0,0 +1,18 @@
# Elm
Elm has one of the best developer experiences that I'm aware of. The error
messages are helpful and the entire experience is optimized to improve the ease
of writing web applications.
## Developing
If you're interested in contributing, the following will create an environment
in which you can develop:
```shell
$ nix-shell
$ npx tailwindcss build index.css -o output.css
$ elm-live -- src/Main.elm --output=Main.min.js
```
You can now view your web client at `http://localhost:8000`!

View file

@ -0,0 +1,32 @@
{
"type": "application",
"source-directories": [
"src"
],
"elm-version": "0.19.1",
"dependencies": {
"direct": {
"elm/browser": "1.0.2",
"elm/core": "1.0.5",
"elm/html": "1.0.0",
"elm/random": "1.0.0",
"elm/svg": "1.0.1",
"elm/time": "1.0.0",
"elm-community/list-extra": "8.2.3",
"elm-community/maybe-extra": "5.2.0",
"elm-community/random-extra": "3.1.0",
"justinmimbs/date": "3.2.1"
},
"indirect": {
"elm/json": "1.1.3",
"elm/parser": "1.1.0",
"elm/url": "1.0.0",
"elm/virtual-dom": "1.0.2",
"owanturist/elm-union-find": "1.0.0"
}
},
"test-dependencies": {
"direct": {},
"indirect": {}
}
}

View file

@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View file

@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Elm SPA</title>
<link rel="stylesheet" href="./output.css" />
<script src="./Main.min.js"></script>
</head>
<body>
<div id="mount"></div>
<script>
Elm.Main.init({node: document.getElementById("mount")});
</script>
</body>
</html>

View file

@ -0,0 +1,10 @@
let
briefcase = import <briefcase> {};
pkgs = briefcase.third_party.pkgs;
in pkgs.mkShell {
buildInputs = with pkgs.elmPackages; [
elm
elm-format
elm-live
];
}

View file

@ -0,0 +1,169 @@
module Habits exposing (render)
import Browser
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (..)
import Set
import State
import Time exposing (Weekday(..))
morning : List State.Habit
morning =
[ "Make bed"
, "Brush teeth"
, "Shower"
, "Do push-ups"
, "Meditate"
]
evening : List State.Habit
evening =
[ "Read (30 minutes)"
, "Record in State.Habit Journal"
]
monday : List State.Habit
monday =
[ "Bikram Yoga @ 17:00 (90 min)"
]
tuesday : List State.Habit
tuesday =
[ "Bikram Yoga @ 18:00 (90 min)"
]
wednesday : List State.Habit
wednesday =
[ "Shave"
, "Bikram Yoga @ 17:00 (90 min)"
]
thursday : List State.Habit
thursday =
[]
friday : List State.Habit
friday =
[ "Bikram Yoga @ 17:00 (60 min)"
, "Take-out trash"
, "Shop for groceries"
]
saturday : List State.Habit
saturday =
[ "Nap"
]
sunday : List State.Habit
sunday =
[ "Shampoo"
, "Shave"
, "Trim nails"
, "Combine trash cans"
, "Mop tile and wood floors"
, "Laundry"
, "Vacuum bedroom"
, "Dust surfaces"
, "Clean mirrors"
, "Clean desk"
]
weekdayName : Weekday -> String
weekdayName weekday =
case weekday of
Mon ->
"Monday"
Tue ->
"Tuesday"
Wed ->
"Wednesday"
Thu ->
"Thursday"
Fri ->
"Friday"
Sat ->
"Saturday"
Sun ->
"Sunday"
habitsFor : Weekday -> List State.Habit
habitsFor weekday =
case weekday of
Mon ->
monday
Tue ->
tuesday
Wed ->
wednesday
Thu ->
thursday
Fri ->
friday
Sat ->
saturday
Sun ->
sunday
tailwind : List ( String, Bool ) -> Attribute msg
tailwind classes =
classes
|> List.filter (\( k, v ) -> v)
|> List.map (\( k, v ) -> k)
|> String.join " "
|> class
render : State.Model -> Html State.Msg
render { dayOfWeek, completed } =
case dayOfWeek of
Nothing ->
p [] [ text "Unable to display habits because we do not know what day of the week it is." ]
Just weekday ->
div [ class "font-mono py-6 px-6" ]
[ h1 [ class "text-2xl text-center" ] [ text (weekdayName weekday) ]
, ul []
(weekday
|> habitsFor
|> List.indexedMap
(\i x ->
li [ class "text-xl" ]
[ button
[ class "py-5 px-6"
, tailwind
[ ( "line-through"
, Set.member i completed
)
]
, onClick (State.ToggleHabit i)
]
[ text x ]
]
)
)
]

View file

@ -0,0 +1,27 @@
module Main exposing (main)
import Browser
import Habits
import Html exposing (..)
import State
subscriptions : State.Model -> Sub State.Msg
subscriptions model =
Sub.none
view : State.Model -> Html State.Msg
view model =
case model.view of
State.Habits ->
Habits.render model
main =
Browser.element
{ init = \() -> State.init
, subscriptions = subscriptions
, update = State.update
, view = view
}

View file

@ -0,0 +1,80 @@
module State exposing (..)
import Date
import Set exposing (Set)
import Task
import Time exposing (Weekday(..))
type Msg
= DoNothing
| SetView View
| ReceiveDate Date.Date
| ToggleHabit Int
type View
= Habits
type HabitType
= Daily
| Weekly
| Yearly
type alias Habit =
String
type alias Model =
{ isLoading : Bool
, view : View
, dayOfWeek : Maybe Weekday
, completed : Set Int
}
{-| The initial state for the application.
-}
init : ( Model, Cmd Msg )
init =
( { isLoading = False
, view = Habits
, dayOfWeek = Nothing
, completed = Set.empty
}
, Date.today |> Task.perform ReceiveDate
)
{-| Now that we have state, we need a function to change the state.
-}
update : Msg -> Model -> ( Model, Cmd Msg )
update msg ({ completed } as model) =
case msg of
DoNothing ->
( model, Cmd.none )
SetView x ->
( { model
| view = x
, isLoading = True
}
, Cmd.none
)
ReceiveDate x ->
( { model | dayOfWeek = Just Sun }, Cmd.none )
ToggleHabit i ->
( { model
| completed =
if Set.member i completed then
Set.remove i completed
else
Set.insert i completed
}
, Cmd.none
)

View file

@ -0,0 +1,43 @@
# Habit Screens
## MVP
One Android tablet mounted on my bedroom wall displaying habits for that day. I
can toggle the done/todo states on each item by tapping it. There is no
server. All of the habits are defined in the client-side codebase. The
application is available online at wpcarro.dev.
## Ideal
Three Android tablets: one mounted in my bedroom, another in my bathroom, and a
third in my kitchen. Each tablet has a view of the current state of the
application and updates in soft real-time.
I track the rates at which I complete each habit and compile all of the metrics
into a dashboard. When I move a habit from Saturday to Sunday or from Wednesday
to Monday, it doesn't break the tracking.
When I complete a habit, it quickly renders some consistency information like
"completing rate since Monday" and "length of current streak".
I don't consider this application that sensitive, but for security purposes I
would like this application to be accessible within a private network. This is
something I don't know too much about setting up, but I don't want anyone to be
able to visit www.BillAndHisHabits.com and change the states of my habits and
affect the tracking data. Nor do I want anyone to be able to make HTTP requests
to my server to alter the state of the application without my permission.
## Client
Language: Elm
### Updates across devices
Instead of setting up sockets on my server and subscribing to them from the
client, I think each device should poll the server once every second (or fewer)
to maintain UI consistency.
## Server
Language: Haskell
Database: SQLite