From 46940ba59d29648b0231d0106d52da6def1677ad Mon Sep 17 00:00:00 2001 From: freeram Date: Thu, 4 Apr 2024 07:41:48 -0600 Subject: [PATCH] The great restructuring 2 --- pomodorodiscord/run.py | 37 +----------- pomodorodiscord/src/__init__.py | 0 pomodorodiscord/src/app.py | 34 +++++++++++ pomodorodiscord/src/components/entry_frame.py | 22 +++++++ .../src/components/statistic_display.py | 17 ++++++ .../src/{ => frames}/pomodoro_frame.py | 12 +--- .../src/{ => frames}/settings_frame.py | 58 ++++-------------- .../src/{ => frames}/stats_frame.py | 59 +++++-------------- pomodorodiscord/src/{ => logic}/graphs.py | 6 +- .../src/{ => logic}/richpresence.py | 4 +- pomodorodiscord/src/utils.py | 15 ++--- 11 files changed, 116 insertions(+), 148 deletions(-) delete mode 100644 pomodorodiscord/src/__init__.py create mode 100644 pomodorodiscord/src/app.py create mode 100644 pomodorodiscord/src/components/entry_frame.py create mode 100644 pomodorodiscord/src/components/statistic_display.py rename pomodorodiscord/src/{ => frames}/pomodoro_frame.py (93%) rename pomodorodiscord/src/{ => frames}/settings_frame.py (70%) rename pomodorodiscord/src/{ => frames}/stats_frame.py (72%) rename pomodorodiscord/src/{ => logic}/graphs.py (94%) rename pomodorodiscord/src/{ => logic}/richpresence.py (89%) diff --git a/pomodorodiscord/run.py b/pomodorodiscord/run.py index 28ad0e0..bc52dba 100644 --- a/pomodorodiscord/run.py +++ b/pomodorodiscord/run.py @@ -1,42 +1,7 @@ import customtkinter as ctk -from src.pomodoro_frame import PomodoroFrame -from src.settings_frame import SettingsFrame -from src.stats_frame import StatsFrame +from src.app import PomodoroApp from src.utils import load_config - -class TabView(ctk.CTkTabview): - def __init__(self, master, **kwargs): - super().__init__(master, **kwargs) - - self.add("Main") - self.add("Settings") - self.add("Stats") - - self.main_frame = PomodoroFrame(self.tab("Main")) - self.main_frame.pack(expand=True, fill='both') - - self.settings_frame = SettingsFrame(self.tab("Settings")) - self.settings_frame.pack(expand=True, fill='both') - - self.stats_frame = StatsFrame(self.tab("Stats")) - self.stats_frame.pack(expand=True, fill='both') - - -class PomodoroApp(ctk.CTk): - WIDTH = 350 - HEIGHT = 400 - - def __init__(self): - super().__init__() - - self.title("Pomodoro Tracker") - self.geometry(f"{PomodoroApp.WIDTH}x{PomodoroApp.HEIGHT}") - - self.tabview = TabView(master=self) - self.tabview.pack(pady=(15, 30), expand=True, fill='y') - - if __name__ == "__main__": config = load_config() ctk.set_default_color_theme(f"themes/{config['theme']}.json") diff --git a/pomodorodiscord/src/__init__.py b/pomodorodiscord/src/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/pomodorodiscord/src/app.py b/pomodorodiscord/src/app.py new file mode 100644 index 0000000..6bc0e50 --- /dev/null +++ b/pomodorodiscord/src/app.py @@ -0,0 +1,34 @@ +import customtkinter as ctk +from src.frames.pomodoro_frame import PomodoroFrame +from src.frames.settings_frame import SettingsFrame +from src.frames.stats_frame import StatsFrame + + +class TabView(ctk.CTkTabview): + def __init__(self, master, **kwargs): + super().__init__(master, **kwargs) + self.add("Main") + self.add("Settings") + self.add("Stats") + + self.main_frame = PomodoroFrame(self.tab("Main")) + self.main_frame.pack(expand=True, fill='both') + + self.settings_frame = SettingsFrame(self.tab("Settings")) + self.settings_frame.pack(expand=True, fill='both') + + self.stats_frame = StatsFrame(self.tab("Stats")) + self.stats_frame.pack(expand=True, fill='both') + + +class PomodoroApp(ctk.CTk): + WIDTH = 350 + HEIGHT = 400 + + def __init__(self): + super().__init__() + self.title("Pomodoro Tracker") + self.geometry(f"{PomodoroApp.WIDTH}x{PomodoroApp.HEIGHT}") + + self.tabview = TabView(master=self) + self.tabview.pack(pady=(15, 30), expand=True, fill='y') diff --git a/pomodorodiscord/src/components/entry_frame.py b/pomodorodiscord/src/components/entry_frame.py new file mode 100644 index 0000000..906a73b --- /dev/null +++ b/pomodorodiscord/src/components/entry_frame.py @@ -0,0 +1,22 @@ +import customtkinter as ctk + + +class EntryFrame(ctk.CTkFrame): + def __init__(self, master, text, config, config_attr, defvalue, command): + super().__init__(master) + + self.label = ctk.CTkLabel(self, text=text) + self.label.pack(pady=(10, 10), padx=(10, 10)) + + self.controls_frame = ctk.CTkFrame(self) + self.controls_frame.pack(fill=ctk.X, expand=True, padx=10) + + self.entry_var = ctk.IntVar(value=config.get(config_attr, defvalue)) + self.entry = ctk.CTkEntry(self.controls_frame, width=35, textvariable=self.entry_var) + self.entry.pack(side=ctk.LEFT, fill=ctk.X, expand=True, padx=(0, 10)) + + self.set_button = ctk.CTkButton(self.controls_frame, width=120, text="Set", command=command) + self.set_button.pack(side=ctk.RIGHT) + + def get(self): + return self.entry_var.get() diff --git a/pomodorodiscord/src/components/statistic_display.py b/pomodorodiscord/src/components/statistic_display.py new file mode 100644 index 0000000..dc56bfb --- /dev/null +++ b/pomodorodiscord/src/components/statistic_display.py @@ -0,0 +1,17 @@ +import customtkinter as ctk + + +class StatisticDisplay(ctk.CTkFrame): + def __init__(self, master, title, initial_value="0", title_font=18, val_font=24, **kwargs): + super().__init__(master, **kwargs) + self.pack(pady=(10, 15), fill="x") + + self.title_label = ctk.CTkLabel(self, text=title, font=("Helvetica", title_font), anchor="n") + self.title_label.pack(fill="x") + + self.value_var = ctk.StringVar(value=initial_value) + self.value_label = ctk.CTkLabel(self, textvariable=self.value_var, font=("Helvetica", val_font), anchor="center") + self.value_label.pack(pady=(5, 0), fill="x") + + def set_value(self, value): + self.value_var.set(value) diff --git a/pomodorodiscord/src/pomodoro_frame.py b/pomodorodiscord/src/frames/pomodoro_frame.py similarity index 93% rename from pomodorodiscord/src/pomodoro_frame.py rename to pomodorodiscord/src/frames/pomodoro_frame.py index cf436e4..5c147d4 100644 --- a/pomodorodiscord/src/pomodoro_frame.py +++ b/pomodorodiscord/src/frames/pomodoro_frame.py @@ -2,15 +2,14 @@ import threading import customtkinter as ctk from datetime import datetime, timedelta -from src.utils import load_config, load_data, save_data, DEF_POMODORO_MINS, DEF_SB_MINS, DEF_LB_MINS, DEF_SB_BEFORE_L, beep -from src.richpresence import RichPresence +from src.utils import load_config, load_data, save_data, beep, DEF_POMODORO_MINS, DEF_SB_MINS, DEF_LB_MINS, DEF_SB_BEFORE_L +from src.logic.richpresence import RichPresence BREAK_BTN_COLOR = "#9a9a9a" BREAK_HOVER = "#adaaaa" RESET_BTN_COLOR = "#cca508" RESET_HOVER = "#e3b707" -# TODO: clean everything class PomodoroFrame(ctk.CTkFrame): def __init__(self, master): @@ -22,7 +21,6 @@ def __init__(self, master): threading.Thread(target=self.initialize_rpc, daemon=True).start() def initialize_ui(self, config): - """Set up the UI components for the Pomodoro timer""" # Helper text that appears when a break is running self.break_text = ctk.StringVar(value="") self.break_label = ctk.CTkLabel(self, textvariable=self.break_text, font=("Roboto", 15)) @@ -49,8 +47,6 @@ def initialize_ui(self, config): self.reset_button.pack() def initialize_state(self, config): - """Initialize the state variables""" - # Main self.running = False self.break_running = False self.next_timer_update = None @@ -72,7 +68,6 @@ def initialize_state(self, config): self.seconds_studied = 0 def initialize_rpc(self): - """Start the rich presence in a separate thread""" self.rpc = RichPresence() self.rpc_thread = threading.Thread(target=self.update_rpc, daemon=True) self.rpc_thread.start() @@ -84,7 +79,7 @@ def update_rpc(self): elif self.running: self.rpc.running_state(self.session_counter + 1, self.start_time_timestamp, self.end_time_timestamp) else: - self.rpc.default_state(self.end_time_timestamp) + self.rpc.default_state() # Discord-imposed rate limit time.sleep(15) @@ -138,7 +133,6 @@ def reset(self, to:str="pomodoro_time", default:int=DEF_POMODORO_MINS): self.next_timer_update = None config = load_config() - # TODO: Make cleaner? self.pomodoro_time = int(config.get(to, default) * 60) self.auto_break_cycling = config.get("auto_break_cycling", False) self.short_break_counter = 0 if not self.auto_break_cycling else self.short_break_counter diff --git a/pomodorodiscord/src/settings_frame.py b/pomodorodiscord/src/frames/settings_frame.py similarity index 70% rename from pomodorodiscord/src/settings_frame.py rename to pomodorodiscord/src/frames/settings_frame.py index d6955e5..c7e2d86 100644 --- a/pomodorodiscord/src/settings_frame.py +++ b/pomodorodiscord/src/frames/settings_frame.py @@ -1,28 +1,7 @@ import os import customtkinter as ctk -from src.utils import load_config, save_config, reload_app, DEF_POMODORO_MINS, DEF_SB_MINS, DEF_LB_MINS, DEF_SB_BEFORE_L, beep - - -class EntryFrame(ctk.CTkFrame): - def __init__(self, master, text, config, config_attr, defvalue, command): - super().__init__(master) - - self.label = ctk.CTkLabel(self, text=text) - self.label.pack(pady=(10, 10), padx=(10, 10)) - - # Inner frame to hold the entry and button - self.controls_frame = ctk.CTkFrame(self) - self.controls_frame.pack(fill=ctk.X, expand=True, padx=10) - - self.entry_var = ctk.IntVar(value=config.get(config_attr, defvalue)) - self.entry = ctk.CTkEntry(self.controls_frame, width=35, textvariable=self.entry_var) - self.entry.pack(side=ctk.LEFT, fill=ctk.X, expand=True, padx=(0, 10)) - - self.set_button = ctk.CTkButton(self.controls_frame, width=120, text="Set", command=command) - self.set_button.pack(side=ctk.RIGHT) - - def get(self): - return self.entry_var.get() +from src.components.entry_frame import EntryFrame +from src.utils import load_config, save_config, reload_app, beep, DEF_POMODORO_MINS, DEF_SB_MINS, DEF_LB_MINS, DEF_SB_BEFORE_L class SettingsFrame(ctk.CTkScrollableFrame): @@ -30,50 +9,39 @@ def __init__(self, master): super().__init__(master) self.themes_dir = 'themes' config = load_config() - - # Automatic Break Cycling + self.abcycling_var = ctk.IntVar(value=config.get("auto_break_cycling", 0)) self.abcycling_switch = ctk.CTkCheckBox(self, text=" Automatic break cycling", border_width=2, variable=self.abcycling_var, onvalue=1, offvalue=0, command=self.change_abcycling) self.abcycling_switch.pack(pady=(10, 0)) - - # Short Breaks Before Long Break + self.sb_before_l_entry = EntryFrame(self, "Short breaks before\nlong break (if auto cycling):", config, "short_breaks_before_long", DEF_SB_BEFORE_L, self.change_sb_before_l) self.sb_before_l_entry.pack(pady=(10, 0)) - - # Pomodoro Duration + self.pomodoro_entry = EntryFrame(self, "Pomodoro Duration (mins):", config, "pomodoro_time", DEF_POMODORO_MINS, self.change_pomodoro_time) self.pomodoro_entry.pack(pady=(5, 0)) - - # Short Break Duration + self.sb_entry = EntryFrame(self, "Short Break Duration (mins):", config, "short_break_time", DEF_SB_MINS, self.change_sb_time) self.sb_entry.pack(pady=(5, 0)) - - # Long Break Duration + self.lb_entry = EntryFrame(self, "Long Break Duration (mins):", config, "long_break_time", DEF_LB_MINS, self.change_lb_time) self.lb_entry.pack(pady=(5, 0)) - - # Selecting theme - self.theme_label = ctk.CTkLabel(self, text="Select Theme:") + + self.theme_label = ctk.CTkLabel(self, text="Select Theme (RESTARTS APP):") self.theme_label.pack(pady=(20, 0)) self.theme_options = [os.path.splitext(theme)[0] for theme in os.listdir(self.themes_dir) if theme.endswith('.json')] - - selected = ctk.StringVar(value=config.get('theme', 'Default')) + selected = ctk.StringVar(value=config.get('theme', 'Default')) self.theme_menu = ctk.CTkOptionMenu(self, variable=selected, values=self.theme_options, anchor="n", command=self.change_theme) self.theme_menu.pack(pady=(10, 0)) - # Volume Slider self.volume_label = ctk.CTkLabel(self, text="Adjust Beep Volume:") self.volume_label.pack(pady=(20, 0)) - self.volume_slider = ctk.CTkSlider(self, from_=0, to=100, number_of_steps=100, command=self.change_volume) self.volume_slider.pack(pady=(10, 0)) - - # Set the slider to the current volume - self.volume_slider.set(config.get('volume', 10)) # Default volume to 10% if not set + + self.volume_slider.set(config.get('volume', 10)) self.change_volume(config.get('volume', 10)) - - # Play Beep button + self.beep_button = ctk.CTkButton(self, text="Play", width=70, command=beep.play) self.beep_button.pack(pady=(15, 0)) diff --git a/pomodorodiscord/src/stats_frame.py b/pomodorodiscord/src/frames/stats_frame.py similarity index 72% rename from pomodorodiscord/src/stats_frame.py rename to pomodorodiscord/src/frames/stats_frame.py index 23c0a3b..1a002c0 100644 --- a/pomodorodiscord/src/stats_frame.py +++ b/pomodorodiscord/src/frames/stats_frame.py @@ -1,91 +1,62 @@ import customtkinter as ctk from datetime import datetime +from src.components.statistic_display import StatisticDisplay from src.utils import load_data -from src.graphs import graph_pomodoro_sessions, graph_hours_studied - - -class StatisticDisplay(ctk.CTkFrame): - def __init__(self, master, title, initial_value="0", title_font=18, val_font=24, **kwargs): - super().__init__(master, **kwargs) - self.pack(pady=(10, 15), fill="x") - - # Title Label - self.title_label = ctk.CTkLabel(self, text=title, font=("Helvetica", title_font), anchor="n") - self.title_label.pack(fill="x") - - # Value Label - self.value_var = ctk.StringVar(value=initial_value) - self.value_label = ctk.CTkLabel(self, textvariable=self.value_var, font=("Helvetica", val_font), anchor="center") - self.value_label.pack(pady=(5, 0), fill="x") - - def set_value(self, value): - self.value_var.set(value) +from src.logic.graphs import graph_pomodoro_sessions, graph_hours_studied class StatsFrame(ctk.CTkScrollableFrame): def __init__(self, master): super().__init__(master) - - # Time Studied Today + self.time_today = StatisticDisplay(self, "Time Studied Today:") - - # Pomodoros Today self.pomodoros_today = StatisticDisplay(self, "Pomodoros Today:") - - # Total Hours Studied self.total_hours = StatisticDisplay(self, "Total Time Studied:") - - # Total Pomodoros self.total_pomodoros = StatisticDisplay(self, "Total Pomodoros:") - - # Update Button + self.update_stats = ctk.CTkButton(self, text="Update", width=90, font=("Roboto", 16), command=self.load_stats) self.update_stats.pack(pady=(10, 0)) - - # Graphs + self.graph_label_1 = ctk.CTkLabel(self, text="Pomodoro Sessions Graph", font=("Helvetica", 18)) self.graph_label_1.pack(pady=(32, 8)) self.graph_button_1 = ctk.CTkButton(self, text="Show", width=90, font=("Roboto", 16), command=self.show_sessions_graph) self.graph_button_1.pack() - + self.graph_label_2 = ctk.CTkLabel(self, text="Hours Studied Graph", font=("Helvetica", 18)) self.graph_label_2.pack(pady=(20, 8)) self.graph_button_2 = ctk.CTkButton(self, text="Show", width=90, font=("Roboto", 16), command=self.show_hours_graph) self.graph_button_2.pack() - + self.load_stats() - + def load_stats(self): data = load_data() if not data: return - + current_date = datetime.now().strftime("%Y-%m-%d") total_today_seconds = data.get('seconds_by_date', {}).get(current_date, 0) - total_today_hours = total_today_seconds / 3600 - if total_today_hours < 1: self.time_today.set_value(f"{total_today_seconds // 60} minute{'s' if total_today_seconds // 60 != 1 else ''}") else: self.time_today.set_value(f"{total_today_hours:.1f} hours") - + total_today_pomodoros = data.get('sessions_by_date', {}).get(current_date, 0) self.pomodoros_today.set_value(f"{total_today_pomodoros} session{'s' if total_today_pomodoros != 1 else ''}") - - total_seconds = data.get('total_seconds_studed', 0) + + total_seconds = data.get('total_seconds_studied', 0) total_hours = data.get('total_seconds_studied', 0) / 3600 - if total_hours < 1: self.total_hours.set_value(f"{total_seconds // 60} minute{'s' if total_seconds // 60 != 1 else ''}") else: self.total_hours.set_value(f"{total_hours:.1f} hours") - + total_pomodoros = data.get('total_pomodoro_sessions', 0) self.total_pomodoros.set_value(f"{total_pomodoros} session{'s' if total_pomodoros != 1 else ''}") - + def show_sessions_graph(self): graph_pomodoro_sessions(load_data()) - + def show_hours_graph(self): graph_hours_studied(load_data()) diff --git a/pomodorodiscord/src/graphs.py b/pomodorodiscord/src/logic/graphs.py similarity index 94% rename from pomodorodiscord/src/graphs.py rename to pomodorodiscord/src/logic/graphs.py index 17c7839..8121d24 100644 --- a/pomodorodiscord/src/graphs.py +++ b/pomodorodiscord/src/logic/graphs.py @@ -4,10 +4,10 @@ import matplotlib.dates as mdates from datetime import datetime, timedelta -# Making sure you can run `python3 src/graphs.py` +# Making sure you can run `python3 src/logic/graphs.py` current_dir = os.path.dirname(os.path.abspath(__file__)) -parent_dir = os.path.dirname(current_dir) -sys.path.append(parent_dir) +src = os.path.dirname(os.path.dirname(current_dir)) +sys.path.append(src) from src.utils import load_data diff --git a/pomodorodiscord/src/richpresence.py b/pomodorodiscord/src/logic/richpresence.py similarity index 89% rename from pomodorodiscord/src/richpresence.py rename to pomodorodiscord/src/logic/richpresence.py index 847caac..c26c3cc 100644 --- a/pomodorodiscord/src/richpresence.py +++ b/pomodorodiscord/src/logic/richpresence.py @@ -24,8 +24,8 @@ def format_time(self, seconds_studied): else: return f"{total_hours:.1f} hours" - def default_state(self, start_time=None): - self.update(state="Idling", details=None, start=start_time or self.launch_time, large_image="graytomato", + def default_state(self): + self.update(state="Idling", details=None, start=datetime.now().timestamp(), large_image="graytomato", large_text="github.com/freeram/pomodoro-discord") def running_state(self, session, start_time, end_time): diff --git a/pomodorodiscord/src/utils.py b/pomodorodiscord/src/utils.py index 9345418..0928506 100644 --- a/pomodorodiscord/src/utils.py +++ b/pomodorodiscord/src/utils.py @@ -1,16 +1,16 @@ +import json import os import sys -import json from pygame import mixer + mixer.init() beep = mixer.Sound('sounds/beep.mp3') + DEF_POMODORO_MINS = 25 DEF_SB_MINS = 5 DEF_LB_MINS = 15 - -# Default amount of short breaks before a long break (for auto break cycling) DEF_SB_BEFORE_L = 3 CONFIG_FILE = 'config.json' @@ -23,7 +23,6 @@ def load_file(filename, on_no_file=None): data = json.load(file) except FileNotFoundError: return on_no_file - return data @@ -31,8 +30,7 @@ def load_data(): return load_file(DATA_FILE, {'total_seconds_studied': 0, 'total_pomodoro_sessions': 0, 'seconds_by_date': {}, - 'sessions_by_date': {} - }) + 'sessions_by_date': {}}) def save_data(data): @@ -41,9 +39,8 @@ def save_data(data): def load_config(): - return load_file(CONFIG_FILE, {'theme': 'Default', - 'sound': 'beep.mp3' - }) + return load_file(CONFIG_FILE, {'theme': 'Default', + 'sound': 'beep.mp3'}) def save_config(config):