about summary refs log tree commit diff
diff options
context:
space:
mode:
authorWilliam Carroll <wpcarro@gmail.com>2020-10-10T16·04+0100
committerWilliam Carroll <wpcarro@gmail.com>2020-10-10T16·04+0100
commit9d331f307747d062ea9de07f553864cff9ed918a (patch)
tree7a14b57aff15c8f6c13b3a9745d4b17385ed494b
parent02ce74eada9df71f760bef4dc0eccddab8d6fbfe (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.md12
-rw-r--r--scratch/habit-screens/client/.envrc2
-rw-r--r--scratch/habit-screens/client/.gitignore3
-rw-r--r--scratch/habit-screens/client/README.md18
-rw-r--r--scratch/habit-screens/client/elm.json32
-rw-r--r--scratch/habit-screens/client/index.css3
-rw-r--r--scratch/habit-screens/client/index.html15
-rw-r--r--scratch/habit-screens/client/shell.nix10
-rw-r--r--scratch/habit-screens/client/src/Habits.elm169
-rw-r--r--scratch/habit-screens/client/src/Main.elm27
-rw-r--r--scratch/habit-screens/client/src/State.elm80
-rw-r--r--scratch/habit-screens/design.md43
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