Add habits widget
This commit is contained in:
217
community/habits-widget.lua
Normal file
217
community/habits-widget.lua
Normal file
@@ -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
|
||||||
Reference in New Issue
Block a user