From 33f79a302d858fa6af0278e58891fdf8809ad03d Mon Sep 17 00:00:00 2001 From: danceforheaven <100450074+danceforheaven@users.noreply.github.com> Date: Mon, 23 Sep 2024 00:38:46 -0500 Subject: [PATCH] Added lifecounter app --- apps/lifecounter/README.md | 14 +++ apps/lifecounter/life_counter.gif | Bin 0 -> 3841 bytes apps/lifecounter/life_counter.star | 194 +++++++++++++++++++++++++++++ apps/lifecounter/manifest.yaml | 6 + 4 files changed, 214 insertions(+) create mode 100644 apps/lifecounter/README.md create mode 100644 apps/lifecounter/life_counter.gif create mode 100644 apps/lifecounter/life_counter.star create mode 100644 apps/lifecounter/manifest.yaml diff --git a/apps/lifecounter/README.md b/apps/lifecounter/README.md new file mode 100644 index 000000000..ed08ebd29 --- /dev/null +++ b/apps/lifecounter/README.md @@ -0,0 +1,14 @@ +# Life Counter +> How long do you have left? + +If you don't feel overwhelmed enough by your own mortality, download this app - and be reminded every day that your time is finite and depleting. + +## What it does + +Enter your date of birth in the app settings, and a grid of dots representing the months in your life will be displayed. + +Each dot indicates one month, and each column of 24 represents two years. A red dot indicates a month that has already passed, a white dot is one that has not. A flashing dot marks the current month - it will flash faster as the month approaches its end. + +The default number of years shown is 100. This can be changed, preferably to the average life expectancy of whichever demographic you happen to be in. + +![](life_counter.gif) \ No newline at end of file diff --git a/apps/lifecounter/life_counter.gif b/apps/lifecounter/life_counter.gif new file mode 100644 index 0000000000000000000000000000000000000000..5552afb4e5787439283321ef5c039529d66eb65f GIT binary patch literal 3841 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 0: + plot_list.append((i // COLUMN_HEIGHT, -(i % COLUMN_HEIGHT))) + months_remaining[0] -= 1 + +def create_months_plot(months_alive, years_displayed): + # empty list will contain plot, int is number of columns in plot + months_plot = {"filled": [[], 0], "halffull": [[], 0], "halfempty": [[], 0], "empty": [[], 0]} + + months_remaining = [years_displayed * 12] # in list so can be passed by reference + + total_columns = years_displayed * 12 // COLUMN_HEIGHT + if months_remaining[0] % COLUMN_HEIGHT != 0: + total_columns += 1 + months_plot["filled"][1] = min(months_alive // COLUMN_HEIGHT, total_columns) + months_plot["halffull"][1] = 0 if months_alive % COLUMN_HEIGHT == 0 or months_plot["filled"][1] >= total_columns else 1 + months_plot["halfempty"][1] = months_plot["halffull"][1] + months_plot["empty"][1] = max(0, total_columns - (months_plot["filled"][1] + months_plot["halffull"][1])) + + plot_filler(months_plot["filled"][0], months_plot["filled"][1], months_remaining) + if months_plot["halffull"][1] == 1: + for i in range(0, months_alive % COLUMN_HEIGHT): + if months_remaining[0] > 0: + months_plot["halffull"][0].append((0, -(i))) + months_remaining[0] -= 1 + for i in range(0, COLUMN_HEIGHT - (months_alive % COLUMN_HEIGHT)): + if months_remaining[0] > 0: + months_plot["halfempty"][0].append((0, -(i))) + months_remaining[0] -= 1 + plot_filler(months_plot["empty"][0], months_plot["empty"][1], months_remaining) + + print("halffull", months_plot["halffull"][0]) + print("halfempty", months_plot["halfempty"][0]) + return months_plot + +def render_workaround(months_plot, key, color): + if len(months_plot[key][0]) == 1: + return render.Box( + width = 1, + height = 1, + color = color, + ) + else: + return render.Plot( + data = months_plot[key][0], + width = months_plot[key][1], + height = len(months_plot[key][0]), + color = color, + ) + +def main(config): + location = config.get("location", DEFAULT_LOCATION) + timezone = json.decode(location)["timezone"] + date_of_birth = time.parse_time(config.get("date of birth", DEFAULT_DATE_OF_BIRTH)) + years_displayed = int(config.get("years displayed", DEFAULT_YEARS_DISPLAYED)) + if years_displayed % 2 == 1: + years_displayed -= 1 + if years_displayed > 120: + years_displayed = 120 + + current_time = time.now().in_location(timezone) + print(current_time) + print(date_of_birth) + print(timezone) + + months_alive = find_months_alive(current_time, date_of_birth) + print(months_alive) + months_plot = create_months_plot(months_alive, years_displayed) + months_plot_flash = create_months_plot(months_alive + 1, years_displayed) + + return render.Root( + delay = 1000 - (28 * current_time.day), + child = render.Animation(children = [ + render.Column( + main_align = "center", + cross_align = "center", + expanded = True, + children = [ + render.Row( + main_align = "center", + cross_align = "center", + expanded = True, + children = [ + render.Plot( + data = months_plot["filled"][0], + width = months_plot["filled"][1], + height = COLUMN_HEIGHT, + color = "#ff0000", + # chart_type = 'scatter' + ), + render.Column( + children = [ + render_workaround(months_plot, "halffull", "#ff0000"), + render_workaround(months_plot, "halfempty", "#ffffff"), + ], + ), + render.Plot( + data = months_plot["empty"][0], + width = months_plot["empty"][1], + height = COLUMN_HEIGHT, + color = "#ffffff", + # chart_type = 'scatter' + ), + ], + ), + ], + ), + render.Column( + main_align = "center", + cross_align = "center", + expanded = True, + children = [ + render.Row( + main_align = "center", + cross_align = "center", + expanded = True, + children = [ + render.Plot( + data = months_plot_flash["filled"][0], + width = months_plot_flash["filled"][1], + height = COLUMN_HEIGHT, + color = "#ff0000", + # chart_type = 'scatter' + ), + render.Column( + children = [ + render_workaround(months_plot_flash, "halffull", "#ff0000"), + render_workaround(months_plot_flash, "halfempty", "#ffffff"), + ], + ), + render.Plot( + data = months_plot_flash["empty"][0], + width = months_plot_flash["empty"][1], + height = COLUMN_HEIGHT, + color = "#ffffff", + # chart_type = 'scatter' + ), + ], + ), + ], + ), + ]), + ) + +def get_schema(): + return schema.Schema( + version = "1", + fields = [ + schema.Text( + id = "years displayed", + name = "Years Displayed", + desc = "The number of years displayed in dots. Max 120, even numbers only.", + icon = "clock", + ), + schema.DateTime( + id = "date of birth", + name = "Date of birth", + desc = "The date on which you were born.", + icon = "calendar", + ), + schema.Location( + id = "location", + name = "Location", + desc = "Location, used to find your timezone.", + icon = "map", + ), + ], + ) diff --git a/apps/lifecounter/manifest.yaml b/apps/lifecounter/manifest.yaml new file mode 100644 index 000000000..66e9e4cf4 --- /dev/null +++ b/apps/lifecounter/manifest.yaml @@ -0,0 +1,6 @@ +--- +id: life-counter +name: Life Counter +summary: Lifespan elapsed/remaining +desc: Visually represents the months elapsed since DOB and remaining before reaching a user-selected age. +author: danceforheaven