From beb243d4829a689c9bfe7088c6dcf947253fd47b Mon Sep 17 00:00:00 2001 From: Ozi-s Date: Fri, 12 Jun 2026 12:44:36 +0300 Subject: [PATCH] rename main file and fix timings --- focus-timer.py | 237 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 237 insertions(+) create mode 100644 focus-timer.py diff --git a/focus-timer.py b/focus-timer.py new file mode 100644 index 0000000..33c3a50 --- /dev/null +++ b/focus-timer.py @@ -0,0 +1,237 @@ +import tkinter as tk +import os +from datetime import datetime +from customtkinter import CTkScrollableFrame, CTkLabel + +STORAGE_PATH = os.path.expanduser("~/.config/focus-timer/data.txt") + +STATE_IDLE = "IDLE" +STATE_FOCUS = "FOCUS" +STATE_WORK = "WORK" +STATE_OVERTIME = "OVERTIME" +STATE_BREAK = "BREAK" + +current_state = STATE_IDLE +seconds_left = 0 +distractions = 0 + +width = 120 +height = 140 + +app = tk.Tk(className="focustimer") +app.title("Focus-Timer") +app.configure(background="#17153B") +app.geometry(f"{width}x{height}") +app.minsize(width, height) +app.maxsize(width, height) +app.update() +app.resizable(False, False) + + +def tick(): + global seconds_left, current_state + + match current_state: + case "IDLE": + return + case "OVERTIME": + seconds_left += 1 + case _: + seconds_left -= 1 + + update_timer_label() + + if current_state in ("FOCUS","WORK","BREAK") and seconds_left <= 0: + handle_transition() + + app.after(1000, tick) + +def update_timer_label(): + global seconds_left + mins, secs = divmod(seconds_left, 60) + timer_label.config(text=f"{mins}:{secs}") + +def handle_transition(): + global current_state, seconds_left, distraction + + match current_state: + case "FOCUS": + current_state = STATE_WORK + seconds_left = 4200 # 4200 + distractions = 0 + + case "WORK": + current_state = STATE_OVERTIME + seconds_left = 0 + + case "BREAK": + current_state = STATE_IDLE + seconds_left = 0 + + update_timer_label() + update_ui() + +def start_button_f(): + global current_state, seconds_left, distractions + distractions = 0 + current_state = STATE_FOCUS + seconds_left = 1200 # 1200 + + tick() + update_ui() + +def distracted_button_f(): + global current_state, seconds_left, distractions + + if current_state != STATE_FOCUS: + return + + distractions += 1 + + if distractions > 2: + seconds_left = 600 + else: + seconds_left = 1200 - (300 * distractions) + + update_timer_label() + +def break_button_f(): + global current_state, seconds_left + + current_state = STATE_BREAK + seconds_left = 1500 # 1500 + update_timer_label() + + create_post() + update_ui() + +def complete_task_button_f(): + break_button_f() + +def create_post(): + os.makedirs(os.path.dirname(STORAGE_PATH), exist_ok=True) + history = {} + + if os.path.exists(STORAGE_PATH): + with open(STORAGE_PATH, "r", encoding="utf-8") as f: + for line in f: + line = line.strip() + if " : " in line and line.endswith(" cycles."): + date_part, cycles_part = line.split(" : ", maxsplit=1) + count = int(cycles_part.replace(" cycles.", "")) + history[date_part] = count + + date = datetime.now().strftime("%Y-%m-%d") + if date in history: + history[date] += 1 + else: + history[date] = 1 + + with open(STORAGE_PATH, "w", encoding="utf-8") as f: + for dates, counts in history.items(): + f.write(f"{dates} : {counts} cycles.\n") + +def update_ui(): + global current_state + + start_button.pack_forget() + distracted_button.pack_forget() + complete_task_button.pack_forget() + break_button.pack_forget() + + match current_state: + case "IDLE": + text_label.config(text="Wanna start?", fg="#C8ACD6") + timer_label.config(text="00:20:00", fg="#C8ACD6") + + start_button.config(fg="#c8acd6", activebackground="#c8acd6") + start_button.pack(side="bottom",pady="15") + + case "FOCUS": + text_label.config(text="Time to focus!", fg="red") + timer_label.config(fg="red") + distracted_button.pack(side="bottom", pady="15") + + case "WORK": + text_label.config(text="Work.", fg="aqua") + timer_label.config(fg="aqua") + complete_task_button.pack(side="bottom", pady="15") + + case "OVERTIME": + text_label.config(text="Maybe a break?", fg="orange") + timer_label.config(fg="orange") + break_button.pack(side="bottom", pady="15") + + case "BREAK": + text_label.config(text="Relax..", fg="yellow") + timer_label.config(fg="yellow") + + start_button.config(fg="yellow", activebackground="yellow") + start_button.pack(side="bottom", pady="15") + +def open_stats_window(event): + st_win = tk.Toplevel() + st_win.title("Statistics") + st_win.configure(background="#17153b") + st_win.geometry("180x200") + st_win.minsize(180, 200) + st_win.maxsize(180, 200) + st_win.resizable(False, False) + + + lines= [] + if os.path.exists(STORAGE_PATH): + with open(STORAGE_PATH, "r", encoding="utf-8") as f: + for line in f: + line = line.strip() + if line and ":" in line: + lines.append(line) + else: + show_problems_label(st_win) + + + if lines: + scroll_frame = CTkScrollableFrame(st_win) + scroll_frame.configure(fg_color="#c8acd6", corner_radius=10) + scroll_frame.pack(fill="both", expand=True, padx=10, pady=15) + with open(STORAGE_PATH, "r", encoding="utf-8") as f: + line_c = 1 + for line in f: + line = line.strip() + if line_c % 2 == 0: + label_c1 = CTkLabel(scroll_frame, text=line, font=("Helvetica", 16), fg_color="#975fb3", text_color="black", corner_radius=15) + label_c1.pack(side="top", fill="x", pady=2, expand=True) + else: + label_c2 = CTkLabel(scroll_frame, text=line, font=("Helvetica", 16), fg_color="#af74cc", text_color="black", corner_radius=15) + label_c2.pack(side="top", fill="x", pady=5, expand=True) + line_c += 1 + else: + show_problems_label(st_win) + + +def show_problems_label(master): + info_label = tk.Label(master, text="No one entries have been\n registered yet.\n Or file does not exist. \n :(", bg="#17153b", fg="#C8ACD6", font=("Helvetica", 16)) + info_label.pack(anchor="center", fill="both", expand=True) + +text_label = tk.Label(app, font=("Helvetica", 14, "bold"), bg="#17153b") +text_label.pack(side="top", pady="15") + +timer_label = tk.Label(app, text="hh:mm:ss",fg="#C8ACD6",background="#17153B",font=("Helvetica", 25) , anchor="center") +timer_label.pack(expand=True,fill="y") + + +start_button = tk.Button(app, text="Start.", command=start_button_f, activeforeground="black", background="#17153b", relief="flat", bd="0") + +distracted_button = tk.Button(app, text="I got distracted.", command=distracted_button_f, fg="red", bg="#17153b", relief="flat", bd="0", activebackground="red", activeforeground="black") + +complete_task_button = tk.Button(app, text="Complete the task.", command=complete_task_button_f, fg="aqua", bg="#17153b", relief="flat", bd="0", activebackground="aqua", activeforeground="black") + +break_button = tk.Button(app, text="Go break :3", command=break_button_f, fg="orange", bg="#17153b", relief="flat", bd="0", activebackground="orange", activeforeground="black") + + +app.bind("", open_stats_window) +app.bind("", open_stats_window) + + +update_ui() +app.mainloop()