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()