diff options
author | William Carroll <wpcarro@gmail.com> | 2020-10-10T16·04+0100 |
---|---|---|
committer | William Carroll <wpcarro@gmail.com> | 2020-10-10T16·04+0100 |
commit | 9d331f307747d062ea9de07f553864cff9ed918a (patch) | |
tree | 7a14b57aff15c8f6c13b3a9745d4b17385ed494b | |
parent | 02ce74eada9df71f760bef4dc0eccddab8d6fbfe (diff) |
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.
-rw-r--r-- | scratch/habit-screens/README.md | 12 | ||||
-rw-r--r-- | scratch/habit-screens/client/.envrc | 2 | ||||
-rw-r--r-- | scratch/habit-screens/client/.gitignore | 3 | ||||
-rw-r--r-- | scratch/habit-screens/client/README.md | 18 | ||||
-rw-r--r-- | scratch/habit-screens/client/elm.json | 32 | ||||
-rw-r--r-- | scratch/habit-screens/client/index.css | 3 | ||||
-rw-r--r-- | scratch/habit-screens/client/index.html | 15 | ||||
-rw-r--r-- | scratch/habit-screens/client/shell.nix | 10 | ||||
-rw-r--r-- | scratch/habit-screens/client/src/Habits.elm | 169 | ||||
-rw-r--r-- | scratch/habit-screens/client/src/Main.elm | 27 | ||||
-rw-r--r-- | scratch/habit-screens/client/src/State.elm | 80 | ||||
-rw-r--r-- | scratch/habit-screens/design.md | 43 |
12 files changed, 414 insertions, 0 deletions
diff --git a/scratch/habit-screens/README.md b/scratch/habit-screens/README.md new file mode 100644 index 000000000000..f0a5be9cabf8 --- /dev/null +++ b/scratch/habit-screens/README.md @@ -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"). diff --git a/scratch/habit-screens/client/.envrc b/scratch/habit-screens/client/.envrc new file mode 100644 index 000000000000..a4a62da526d3 --- /dev/null +++ b/scratch/habit-screens/client/.envrc @@ -0,0 +1,2 @@ +source_up +use_nix diff --git a/scratch/habit-screens/client/.gitignore b/scratch/habit-screens/client/.gitignore new file mode 100644 index 000000000000..1cb4f3034cc3 --- /dev/null +++ b/scratch/habit-screens/client/.gitignore @@ -0,0 +1,3 @@ +/elm-stuff +/Main.min.js +/output.css diff --git a/scratch/habit-screens/client/README.md b/scratch/habit-screens/client/README.md new file mode 100644 index 000000000000..04804ad94fac --- /dev/null +++ b/scratch/habit-screens/client/README.md @@ -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`! diff --git a/scratch/habit-screens/client/elm.json b/scratch/habit-screens/client/elm.json new file mode 100644 index 000000000000..6839ac4fabdc --- /dev/null +++ b/scratch/habit-screens/client/elm.json @@ -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": {} + } +} diff --git a/scratch/habit-screens/client/index.css b/scratch/habit-screens/client/index.css new file mode 100644 index 000000000000..b5c61c956711 --- /dev/null +++ b/scratch/habit-screens/client/index.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/scratch/habit-screens/client/index.html b/scratch/habit-screens/client/index.html new file mode 100644 index 000000000000..bdc421ee3f09 --- /dev/null +++ b/scratch/habit-screens/client/index.html @@ -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> diff --git a/scratch/habit-screens/client/shell.nix b/scratch/habit-screens/client/shell.nix new file mode 100644 index 000000000000..00bb4b0b3edc --- /dev/null +++ b/scratch/habit-screens/client/shell.nix @@ -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 + ]; +} diff --git a/scratch/habit-screens/client/src/Habits.elm b/scratch/habit-screens/client/src/Habits.elm new file mode 100644 index 000000000000..e6fd606f5dbe --- /dev/null +++ b/scratch/habit-screens/client/src/Habits.elm @@ -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 ] + ] + ) + ) + ] diff --git a/scratch/habit-screens/client/src/Main.elm b/scratch/habit-screens/client/src/Main.elm new file mode 100644 index 000000000000..d2b36a33f322 --- /dev/null +++ b/scratch/habit-screens/client/src/Main.elm @@ -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 + } diff --git a/scratch/habit-screens/client/src/State.elm b/scratch/habit-screens/client/src/State.elm new file mode 100644 index 000000000000..9e594f9a2462 --- /dev/null +++ b/scratch/habit-screens/client/src/State.elm @@ -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 + ) diff --git a/scratch/habit-screens/design.md b/scratch/habit-screens/design.md new file mode 100644 index 000000000000..f16361ac4358 --- /dev/null +++ b/scratch/habit-screens/design.md @@ -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 |