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:
		
							parent
							
								
									02ce74eada
								
							
						
					
					
						commit
						9d331f3077
					
				
					 12 changed files with 414 additions and 0 deletions
				
			
		
							
								
								
									
										12
									
								
								scratch/habit-screens/README.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								scratch/habit-screens/README.md
									
										
									
									
									
										Normal 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"). | ||||
							
								
								
									
										2
									
								
								scratch/habit-screens/client/.envrc
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								scratch/habit-screens/client/.envrc
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,2 @@ | |||
| source_up | ||||
| use_nix | ||||
							
								
								
									
										3
									
								
								scratch/habit-screens/client/.gitignore
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								scratch/habit-screens/client/.gitignore
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,3 @@ | |||
| /elm-stuff | ||||
| /Main.min.js | ||||
| /output.css | ||||
							
								
								
									
										18
									
								
								scratch/habit-screens/client/README.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								scratch/habit-screens/client/README.md
									
										
									
									
									
										Normal 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`! | ||||
							
								
								
									
										32
									
								
								scratch/habit-screens/client/elm.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								scratch/habit-screens/client/elm.json
									
										
									
									
									
										Normal 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": {} | ||||
|     } | ||||
| } | ||||
							
								
								
									
										3
									
								
								scratch/habit-screens/client/index.css
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								scratch/habit-screens/client/index.css
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,3 @@ | |||
| @tailwind base; | ||||
| @tailwind components; | ||||
| @tailwind utilities; | ||||
							
								
								
									
										15
									
								
								scratch/habit-screens/client/index.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								scratch/habit-screens/client/index.html
									
										
									
									
									
										Normal 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> | ||||
							
								
								
									
										10
									
								
								scratch/habit-screens/client/shell.nix
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								scratch/habit-screens/client/shell.nix
									
										
									
									
									
										Normal 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 | ||||
|   ]; | ||||
| } | ||||
							
								
								
									
										169
									
								
								scratch/habit-screens/client/src/Habits.elm
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										169
									
								
								scratch/habit-screens/client/src/Habits.elm
									
										
									
									
									
										Normal 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 ] | ||||
|                                     ] | ||||
|                             ) | ||||
|                     ) | ||||
|                 ] | ||||
							
								
								
									
										27
									
								
								scratch/habit-screens/client/src/Main.elm
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								scratch/habit-screens/client/src/Main.elm
									
										
									
									
									
										Normal 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 | ||||
|         } | ||||
							
								
								
									
										80
									
								
								scratch/habit-screens/client/src/State.elm
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								scratch/habit-screens/client/src/State.elm
									
										
									
									
									
										Normal 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 | ||||
|             ) | ||||
							
								
								
									
										43
									
								
								scratch/habit-screens/design.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								scratch/habit-screens/design.md
									
										
									
									
									
										Normal 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 | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue