From 7fb84c38e79903949fc74a7ba392be85c669726b Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sat, 5 Jul 2025 07:38:55 +0800 Subject: [PATCH] Add habits widget --- community/habits-widget.lua | 217 ++++++++++++++++++++++++++++++++++++ 1 file changed, 217 insertions(+) create mode 100644 community/habits-widget.lua diff --git a/community/habits-widget.lua b/community/habits-widget.lua new file mode 100644 index 0000000..a50b40b --- /dev/null +++ b/community/habits-widget.lua @@ -0,0 +1,217 @@ +-- name = "Habit tracker" +-- description = "Daily habit tracker with JSON storage" +-- type = "widget" +-- author = "Sergey Mironov" +-- version = "1.0" + +local json = require("json") +local md = require("md_colors") + +local buttons = {} +local filename = "button_data.json" +local dialog_state = nil -- Track current dialog state + +-- Get current date in YYYY-MM-DD format +local function get_current_date() + return os.date("%Y-%m-%d") +end + +-- Load button data from JSON file +local function load_data() + local content = files:read(filename) + if content then + local success, data = pcall(json.decode, content) + if success and data then + buttons = data + else + buttons = {} + end + else + buttons = {} + end +end + +-- Save button data to JSON file +local function save_data() + local content = json.encode(buttons) + files:write(filename, content) +end + +-- Display all buttons +local function display_buttons() + local names = {} + local colors = {} + local current_date = get_current_date() + + -- Add the "+" button at the beginning + table.insert(names, "fa:plus") + table.insert(colors, aio:colors().button) -- Gray color for add button + + -- Add existing buttons + for i, button in ipairs(buttons) do + table.insert(names, button.title) + + -- Check if button was clicked today + if button.last_clicked == current_date then + table.insert(colors, md.green_600) -- Green if clicked today + else + table.insert(colors, button.color) -- Original color + end + end + + ui:show_buttons(names, colors) +end + +-- Handle button clicks +function on_click(index) + local current_date = get_current_date() + + -- Check if it's the "+" button (first button) + if index == 1 then + -- Show add dialog - ask for title first + dialog_state = {action = "add_title"} + dialogs:show_edit_dialog("Add New Button", "Enter button title:", "") + return + end + + -- Handle existing button click (adjust index for "+" button) + local button_index = index - 1 + local button = buttons[button_index] + if button.last_clicked == current_date then + -- Already clicked today, reset + button.last_clicked = "" + ui:show_toast("Unmarked: " .. button.title) + else + -- Not clicked today, mark as clicked + button.last_clicked = current_date + ui:show_toast("Marked: " .. button.title) + end + + save_data() + display_buttons() +end + +-- Handle long clicks for editing +function on_long_click(index) + -- Don't allow long-click on "+" button (first button) + if index == 1 then + return + end + + -- Show edit options (adjust index for "+" button) + local button_index = index - 1 + dialog_state = {action = "edit_options", index = button_index} + local button = buttons[button_index] + dialogs:show_dialog("Edit Button: " .. button.title, "Choose action:", "Edit", "Delete") +end + +-- Handle dialog results +function on_dialog_action(value) + if value == -1 then + -- Cancel pressed + dialog_state = nil + return + end + + if not dialog_state then + return + end + + if dialog_state.action == "add_title" then + if type(value) == "string" and value:trim() ~= "" then + -- Got title, now ask for color + dialog_state = {action = "add_color", title = value} + dialogs:show_edit_dialog("Add New Button\nTitle: " .. value, "Enter color (e.g., #FF0000):", "#FF0000") + else + ui:show_toast("Invalid title") + dialog_state = nil + end + + elseif dialog_state.action == "add_color" then + if type(value) == "string" and value:match("^#%x%x%x%x%x%x$") then + -- Valid color, create button + local new_button = { + title = dialog_state.title, + color = value, + last_clicked = "" + } + table.insert(buttons, new_button) + save_data() + display_buttons() + ui:show_toast("Added: " .. dialog_state.title) + else + ui:show_toast("Invalid color format. Use #RRGGBB") + end + dialog_state = nil + + elseif dialog_state.action == "edit_options" then + if value == 1 then + -- Edit button - ask for new title + local button = buttons[dialog_state.index] + dialog_state = {action = "edit_title", index = dialog_state.index} + dialogs:show_edit_dialog("Edit Button", "Enter new title:", button.title) + elseif value == 2 then + -- Delete button + local button = buttons[dialog_state.index] + table.remove(buttons, dialog_state.index) + save_data() + display_buttons() + ui:show_toast("Deleted: " .. button.title) + dialog_state = nil + end + + elseif dialog_state.action == "edit_title" then + if type(value) == "string" and value:trim() ~= "" then + -- Got new title, now ask for color + local button = buttons[dialog_state.index] + dialog_state = {action = "edit_color", index = dialog_state.index, title = value} + dialogs:show_edit_dialog("Edit Button\nTitle: " .. value, "Enter new color:", button.color) + else + ui:show_toast("Invalid title") + dialog_state = nil + end + + elseif dialog_state.action == "edit_color" then + if type(value) == "string" and value:match("^#%x%x%x%x%x%x$") then + -- Valid color, update button + local button = buttons[dialog_state.index] + button.title = dialog_state.title + button.color = value + save_data() + display_buttons() + ui:show_toast("Updated: " .. button.title) + else + ui:show_toast("Invalid color format. Use #RRGGBB") + end + dialog_state = nil + end +end + +-- Initialize widget +function on_resume() + load_data() + + -- Add some sample data if empty + if #buttons == 0 then + buttons = { + { + title = "Exercise", + color = md.pink_600, + last_clicked = "" + }, + { + title = "Read", + color = md.cyan_600, + last_clicked = "" + }, + { + title = "Water", + color = md.light_blue_600, + last_clicked = "" + } + } + save_data() + end + + display_buttons() +end