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