Motivationstracker erstellt

This commit is contained in:
tea 2026-01-16 22:19:24 +01:00
commit 1d5473fd4f

600
motivations_app_v1_2.py Normal file
View file

@ -0,0 +1,600 @@
# tracker_v1_2.py
# Motivation Tracker, non-threatening UI für gute Laune
# Eingebaute intermittierende Verstärkung (Zufallsgewinnausschüttung) für mehr Motivation
# Scrollable, Matplotlib stats, JSON persistence
# Python 3.8+
import json
import os
import random
from datetime import datetime, timedelta
import tkinter as tk
from tkinter import ttk, messagebox, simpledialog
# Matplotlib (TkAgg backend)
import matplotlib
matplotlib.use("TkAgg")
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import matplotlib.dates as mdates
import matplotlib.font_manager as fm
from matplotlib import rcParams
# -------------------------
# Config
# -------------------------
DATA_FILE = "data_v1_1.json"
PREFERRED_FONT = "Segoe UI" # falls nicht vorhanden, Tk ersetzt automatisch
# default tasks and kaomoji
DEFAULT_TASKS = ["CTF", "HTB", "PYTHON", "DIGITAL GARDENING"]
KAOMOJI = {
"CTF": " ✧(๑ↀᆺↀ๑)✧",
"HTB": " 🌙(˘͈ᵕ ˘͈♡)",
"PYTHON": " 🜁(Φ ω Φ)",
"DIGITAL GARDENING": " 🌿(╹‿╹✿)"
}
DEFAULT_KAO = " ✨(˘͈ᵕ˘͈)✨"
# color theme:
COLORS = {
"bg": "#FBF7FF",
"header": "#EADCF6",
"card": "#FFF4F9",
"muted": "#7B6699",
"accent": "#6B3FA0",
"dark_accent": "#34224A",
"text": "#2E2440",
"positive": "#2E7D32",
"bar1": "#A56CC1",
"bar2": "#4B2C5E"
}
rcParams["font.family"] = [
"Segoe UI Symbol",
"DejaVu Sans"
]
rcParams["figure.dpi"] = 120
rcParams["savefig.dpi"] = 120
# -------------------------
# Data helpers
# -------------------------
def load_data():
if os.path.exists(DATA_FILE):
try:
with open(DATA_FILE, "r", encoding="utf-8") as f:
data = json.load(f)
except Exception:
data = {}
else:
data = {}
data.setdefault("tasks", DEFAULT_TASKS.copy())
data.setdefault("history", {}) # {"YYYY-MM-DD": {task: bool, ...}}
data.setdefault("current_week_points", 0)
data.setdefault("total_earned", 0.0)
data.setdefault("last_payout_date", None)
data.setdefault("payout_history", [])
# Listen wieder in Sets umwandeln (für rewarded_today)
if "rewarded_today" in data:
data["rewarded_today"] = {k: set(v) for k, v in data["rewarded_today"].items()}
return data
def save_data(data):
try:
# Sets in Listen umwandeln für JSON
data_copy = data.copy()
if "rewarded_today" in data_copy:
data_copy["rewarded_today"] = {k: list(v) for k, v in data_copy["rewarded_today"].items()}
with open(DATA_FILE, "w", encoding="utf-8") as f:
json.dump(data_copy, f, indent=2, ensure_ascii=False)
except Exception as e:
print("Fehler beim Speichern:", e)
# -------------------------
# Utility: OS-independent mousewheel binding for canvas
# -------------------------
def bind_canvas_mousewheel(canvas):
def _on_mousewheel(event):
# event.delta on Windows with Tk returns multiples of 120
if event.num == 5 or event.delta < 0:
canvas.yview_scroll(1, "units")
elif event.num == 4 or event.delta > 0:
canvas.yview_scroll(-1, "units")
# Bind/Unbind nur wenn Maus über Canvas ist
def _bound_to_mousewheel(event):
canvas.bind_all("<MouseWheel>", _on_mousewheel)
canvas.bind_all("<Button-4>", _on_mousewheel)
canvas.bind_all("<Button-5>", _on_mousewheel)
def _unbound_to_mousewheel(event):
canvas.unbind_all("<MouseWheel>")
canvas.unbind_all("<Button-4>")
canvas.unbind_all("<Button-5>")
canvas.bind('<Enter>', _bound_to_mousewheel)
canvas.bind('<Leave>', _unbound_to_mousewheel)
# -------------------------
# Main App
# -------------------------
class TrackerApp:
def __init__(self, root):
self.root = root
self.root.title("🔮 Tracker v1.2")
self.root.geometry("900x500")
self.root.minsize(820, 600)
self.data = load_data()
self._ensure_today()
self._build_ui()
# optional: auto check weekly payout on startup if saturday and not paid
self._check_auto_payout_on_startup()
def _ensure_today(self):
today = datetime.now().strftime("%Y-%m-%d")
tasks = self.data.get("tasks", DEFAULT_TASKS.copy())
if today not in self.data["history"]:
self.data["history"][today] = {t: False for t in tasks}
save_data(self.data)
# sync for changed tasks
for _, rec in self.data["history"].items():
for t in self.data["tasks"]:
if t not in rec:
rec[t] = False
# -------------------------
# Build UI (with Scrollbar)
# -------------------------
def _build_ui(self):
# Header
header = tk.Frame(self.root, bg=COLORS["header"], pady=10)
header.pack(fill=tk.X)
tk.Label(header, text="(っ◉ω◉)っ~~☆'。゚.✧.・。゚★'.・.・。゚", bg=COLORS["header"],
fg=COLORS["dark_accent"], font=(PREFERRED_FONT, 18, "bold")).pack()
tk.Label(header, text="Kleine Rituale, viel Magie ✨",
bg=COLORS["header"], fg=COLORS["muted"], font=(PREFERRED_FONT, 10)).pack()
# Scrollable main area: canvas + vertical scrollbar
container = tk.Frame(self.root, bg=COLORS["bg"])
container.pack(fill=tk.BOTH, expand=True, padx=10, pady=8)
self.canvas = tk.Canvas(container, bg=COLORS["bg"], highlightthickness=0)
self.v_scroll = ttk.Scrollbar(container, orient="vertical", command=self.canvas.yview)
self.canvas.configure(yscrollcommand=self.v_scroll.set)
self.v_scroll.pack(side=tk.RIGHT, fill=tk.Y)
self.canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
# inner frame (where content goes)
self.main_frame = tk.Frame(self.canvas, bg=COLORS["bg"])
self.canvas.create_window((0, 0), window=self.main_frame, anchor="nw")
# bind configure to update scrollregion
self.main_frame.bind("<Configure>", lambda e: self.canvas.configure(scrollregion=self.canvas.bbox("all")))
# bind mousewheel / trackpad scrolling
bind_canvas_mousewheel(self.canvas)
# --- add content to main_frame ---
self._build_content_cards()
def _build_content_cards(self):
# week summary card
week_card = tk.Frame(self.main_frame, bg=COLORS["card"], bd=1, relief=tk.RIDGE)
week_card.pack(fill=tk.X, pady=(0, 10))
tk.Label(week_card, text="🌙 Diese Woche gesammelt:", bg=COLORS["card"], fg=COLORS["muted"],
font=(PREFERRED_FONT, 12, "bold")).pack(anchor="w", padx=12, pady=(8, 0))
self.week_label = tk.Label(week_card, text=self._week_text(), bg=COLORS["card"],
fg=COLORS["accent"], font=(PREFERRED_FONT, 16, "bold"))
self.week_label.pack(anchor="w", padx=12, pady=(0, 12))
# tasks section
tasks_card = tk.LabelFrame(self.main_frame, text=f"📅 Heute: {datetime.now().strftime('%d.%m.%Y')}",
bg=COLORS["bg"], fg=COLORS["text"], font=(PREFERRED_FONT, 12, "bold"))
tasks_card.pack(fill=tk.BOTH, pady=(0, 10))
self.task_vars = {}
color_seq = [COLORS["card"], "#FFF6EA", "#E9F7F2", "#F7EAF4"]
today_key = datetime.now().strftime("%Y-%m-%d")
for i, t in enumerate(self.data.get("tasks", DEFAULT_TASKS)):
bg = color_seq[i % len(color_seq)]
row = tk.Frame(tasks_card, bg=bg, bd=1, relief=tk.GROOVE)
row.pack(fill=tk.X, padx=8, pady=6)
var = tk.BooleanVar(value=self.data["history"].get(today_key, {}).get(t, False))
self.task_vars[t] = var
ka = KAOMOJI.get(t, DEFAULT_KAO)
cb_text = f"{t}{ka}"
cb = tk.Checkbutton(row, text=cb_text, variable=var, bg=bg, fg=COLORS["text"],
font=(PREFERRED_FONT, 12), anchor="w",
command=lambda task=t: self._toggle_task(task))
cb.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=6, pady=8)
points = tk.Label(row, text="→ 20 Cent ✨", bg=bg, fg=COLORS["bar2"], font=(PREFERRED_FONT, 10, "bold"))
points.pack(side=tk.RIGHT, padx=8)
# progress card
prog_card = tk.Frame(self.main_frame, bg=COLORS["card"], bd=1, relief=tk.RIDGE)
prog_card.pack(fill=tk.X, pady=(0, 10))
tk.Label(prog_card, text="🔮 Heutiger Fortschritt:", bg=COLORS["card"], fg=COLORS["muted"],
font=(PREFERRED_FONT, 12, "bold")).pack(anchor="w", padx=12, pady=(8, 6))
style = ttk.Style()
style.configure("Horizontal.TProgressbar", troughcolor=COLORS["bg"], background=COLORS["accent"])
self.progress = ttk.Progressbar(prog_card, style="Horizontal.TProgressbar", mode="determinate")
self.progress.pack(fill=tk.X, padx=12, pady=(0, 8))
self.progress_label = tk.Label(prog_card, text="", bg=COLORS["card"], fg=COLORS["text"], font=(PREFERRED_FONT, 11))
self.progress_label.pack(anchor="w", padx=12, pady=(0,8))
# bottom controls area
bottom = tk.Frame(self.main_frame, bg=COLORS["bg"])
bottom.pack(fill=tk.X, pady=(0,12))
total_card = tk.Frame(bottom, bg=COLORS["card"], bd=1, relief=tk.RIDGE)
total_card.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=(0,8))
tk.Label(total_card, text="🌿 Gesamt verdient:", bg=COLORS["card"], fg=COLORS["muted"],
font=(PREFERRED_FONT, 12, "bold")).pack(anchor="w", padx=10, pady=(8,2))
self.total_label = tk.Label(total_card, text=f"{self.data.get('total_earned',0.0):.2f}",
bg=COLORS["card"], fg=COLORS["positive"], font=(PREFERRED_FONT, 14, "bold"))
self.total_label.pack(anchor="w", padx=10, pady=(0,12))
controls = tk.Frame(bottom, bg=COLORS["bg"])
controls.pack(side=tk.RIGHT)
tk.Button(controls, text="📜 Verlauf", command=self.show_history, font=(PREFERRED_FONT, 11), bg=COLORS["card"]).pack(side=tk.LEFT, padx=6)
tk.Button(controls, text="💶 Auszahlungen", command=self.show_payout_history, font=(PREFERRED_FONT, 11), bg=COLORS["card"]).pack(side=tk.LEFT, padx=6)
tk.Button(controls, text="📈 Statistiken", command=self.show_stats, font=(PREFERRED_FONT, 11), bg=COLORS["card"]).pack(side=tk.LEFT, padx=6)
# task management (add/remove/reset)
manage = tk.Frame(self.main_frame, bg=COLORS["bg"])
manage.pack(fill=tk.X, pady=(6,12))
self.new_task_entry = tk.Entry(manage, font=(PREFERRED_FONT, 12))
self.new_task_entry.pack(side=tk.LEFT, padx=(6,6))
tk.Button(manage, text=" Aufgabe hinzufügen", command=self.add_task, font=(PREFERRED_FONT, 11)).pack(side=tk.LEFT, padx=6)
tk.Button(manage, text=" Aufgabe entfernen", command=self.remove_task, font=(PREFERRED_FONT, 11)).pack(side=tk.LEFT, padx=6)
tk.Button(manage, text="♻️ Tag zurücksetzen", command=self.reset_today, font=(PREFERRED_FONT, 11)).pack(side=tk.LEFT, padx=6)
# final update of displays
self.update_display()
# -------------------------
# Toggle logic for tasks
# FIXED: Verhindert Punkt-Exploit durch rewarded_today Tracking
# -------------------------
def _toggle_task(self, task):
today = datetime.now().strftime("%Y-%m-%d")
self.data.setdefault("history", {})
self.data["history"].setdefault(today, {t: False for t in self.data.get("tasks", DEFAULT_TASKS)})
# Neu: Tracking für bereits belohnte Tasks
self.data.setdefault("rewarded_today", {})
if today not in self.data["rewarded_today"]:
self.data["rewarded_today"][today] = set()
was = self.data["history"][today].get(task, False)
nowv = bool(self.task_vars[task].get())
self.data["history"][today][task] = nowv
# Nur Punkte vergeben, wenn Task erstmalig heute abgehakt wird
if nowv and not was and task not in self.data["rewarded_today"][today]:
self.data["current_week_points"] = self.data.get("current_week_points",0) + 1
self.data["rewarded_today"][today].add(task) # Als belohnt markieren
# Tagesbonus nur wenn alle tasks abgehakt und noch nicht alle belohnt wurden
if all(self.data["history"][today].values()):
# Prüfen ob Tagesbonus heute schon vergeben wurde
if not self.data.get("daily_bonus_given", {}).get(today, False):
self.data["current_week_points"] += 2
self.data.setdefault("daily_bonus_given", {})[today] = True
messagebox.showinfo("🌟 Tagesbonus", "Alle Aufgaben erledigt! +2 Bonuspunkte ✨")
# Random surprise nur beim ersten Abhaken
if random.random() < 0.12:
add = random.randint(1, 3)
self.data["current_week_points"] += add
messagebox.showinfo("🔮 Überraschung", f"Du hast {add} Bonuspunkte gewonnen! {DEFAULT_KAO}")
elif not nowv and was:
# Punkte werden nicht abgezogen, da die Belohnung schon vergeben wurde
pass
save_data(self.data)
self.update_display()
# -------------------------
# Add / Remove Tasks
# -------------------------
def add_task(self):
t = self.new_task_entry.get().strip()
if not t:
return
if t in self.data.get("tasks", []):
messagebox.showwarning("Schon vorhanden", "Diese Aufgabe existiert bereits.")
return
self.data["tasks"].append(t)
if t not in KAOMOJI:
KAOMOJI[t] = DEFAULT_KAO
# add to all existing days
for _, rec in self.data["history"].items():
rec[t] = False
save_data(self.data)
messagebox.showinfo("Hinzugefügt", f"Aufgabe '{t}' wurde hinzugefügt. {KAOMOJI[t]}")
self._rebuild_ui() # rebuild so new checkbox appears
def remove_task(self):
t = simpledialog.askstring("Aufgabe entfernen", "Gib den genauen Namen der Aufgabe ein:")
if not t:
return
if t not in self.data.get("tasks", []):
messagebox.showwarning("Nicht gefunden", "Diese Aufgabe existiert nicht.")
return
if not messagebox.askyesno("Bestätigen", f"Soll '{t}' wirklich entfernt werden?"):
return
self.data["tasks"].remove(t)
for day_key in list(self.data["history"].keys()):
if t in self.data["history"][day_key]:
del self.data["history"][day_key][t]
if t in KAOMOJI:
del KAOMOJI[t]
save_data(self.data)
messagebox.showinfo("Entfernt", f"'{t}' wurde entfernt.")
self._rebuild_ui()
def reset_today(self):
today = datetime.now().strftime("%Y-%m-%d")
if today in self.data["history"]:
for k in self.data["history"][today]:
self.data["history"][today][k] = False
# Reset auch rewarded_today und daily_bonus
if today in self.data.get("rewarded_today", {}):
self.data["rewarded_today"][today] = set()
if today in self.data.get("daily_bonus_given", {}):
self.data["daily_bonus_given"][today] = False
save_data(self.data)
self._rebuild_ui()
self.update_display()
messagebox.showinfo("Zurückgesetzt", "Heute wurden alle Aufgaben zurückgesetzt.")
else:
messagebox.showinfo("Kein Eintrag", "Heute gibt es keine Einträge zum Zurücksetzen.")
# -------------------------
# Rebuild UI (simpler approach)
# -------------------------
def _rebuild_ui(self):
# destroy and rebuild to refresh tasks list
for w in self.root.winfo_children():
w.destroy()
# reload data and rebuild
self.data = load_data()
self._ensure_today()
self._build_ui()
# -------------------------
# Display updates
# -------------------------
def _week_text(self):
pts = self.data.get("current_week_points", 0)
euros = pts * 0.20
return f"{pts} Punkte = {euros:.2f}"
def update_display(self):
# update summary labels and progress
self.week_label.config(text=self._week_text())
self.total_label.config(text=f"{self.data.get('total_earned', 0.0):.2f}")
today = datetime.now().strftime("%Y-%m-%d")
total = len(self.data.get("tasks", []))
done = 0
if today in self.data["history"]:
done = sum(1 for v in self.data["history"][today].values() if v)
percent = int(done / total * 100) if total > 0 else 0
self.progress['value'] = percent
self.progress_label.config(text=f"{done}/{total} Tasks erledigt")
# sync checkbox variables
for task, var in self.task_vars.items():
cur = self.data["history"].get(today, {}).get(task, False)
var.set(bool(cur))
# -------------------------
# History & Payout
# -------------------------
def show_history(self):
win = tk.Toplevel(self.root)
win.title("📜 Verlauf")
win.geometry("640x560")
win.configure(bg=COLORS["bg"])
tk.Label(win, text="📚 Verlauf (letzte 30 Tage)", bg=COLORS["bg"], fg=COLORS["dark_accent"],
font=(PREFERRED_FONT, 14, "bold")).pack(pady=8)
cont = tk.Frame(win, bg=COLORS["bg"])
cont.pack(fill=tk.BOTH, expand=True, padx=8, pady=6)
canvas = tk.Canvas(cont, bg=COLORS["bg"], highlightthickness=0)
vs = ttk.Scrollbar(cont, orient="vertical", command=canvas.yview)
canvas.configure(yscrollcommand=vs.set)
vs.pack(side=tk.RIGHT, fill=tk.Y)
canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
frame = tk.Frame(canvas, bg=COLORS["bg"])
canvas.create_window((0,0), window=frame, anchor="nw")
frame.bind("<Configure>", lambda e: canvas.configure(scrollregion=canvas.bbox("all")))
bind_canvas_mousewheel(canvas)
days = sorted(self.data.get("history", {}).keys(), reverse=True)[:30]
for d in days:
rec = self.data["history"].get(d, {})
total = len(rec)
done = sum(1 for v in rec.values() if v)
card = tk.Frame(frame, bg=COLORS["card"], bd=1, relief=tk.RIDGE)
card.pack(fill=tk.X, pady=6, padx=6)
tk.Label(card, text=f"{d}{done}/{total} erledigt", bg=COLORS["card"], fg=COLORS["accent"],
font=(PREFERRED_FONT, 12, "bold")).pack(anchor="w", padx=8, pady=(6,2))
comp = [t for t, v in rec.items() if v]
if comp:
tk.Label(card, text="Erledigt: " + ", ".join(comp), bg=COLORS["card"], fg=COLORS["text"],
font=(PREFERRED_FONT, 10)).pack(anchor="w", padx=8, pady=(0,8))
else:
tk.Label(card, text="Keine Aufgaben erledigt an diesem Tag.", bg=COLORS["card"], fg=COLORS["muted"],
font=(PREFERRED_FONT, 10, "italic")).pack(anchor="w", padx=8, pady=(0,8))
def show_payout_history(self):
win = tk.Toplevel(self.root)
win.title("💶 Auszahlungshistorie")
win.geometry("520x420")
win.configure(bg=COLORS["bg"])
tk.Label(win, text="💰 Auszahlungshistorie", bg=COLORS["bg"], fg=COLORS["accent"],
font=(PREFERRED_FONT, 14, "bold")).pack(pady=8)
ph = list(reversed(self.data.get("payout_history", [])))
if not ph:
tk.Label(win, text="Noch keine Auszahlungen.", bg=COLORS["bg"], fg=COLORS["text"],
font=(PREFERRED_FONT, 11)).pack(pady=18)
return
frame = tk.Frame(win, bg=COLORS["bg"])
frame.pack(fill=tk.BOTH, expand=True, padx=8, pady=6)
for p in ph:
tk.Label(frame, text=f"{p['date']}: {p['amount']:.2f} € ({p['points']} pts)",
bg=COLORS["card"], fg=COLORS["text"], font=(PREFERRED_FONT, 11)).pack(fill=tk.X, padx=8, pady=6)
# manual trigger for weekly payout
def trigger_weekly_payout(self):
pts = self.data.get("current_week_points", 0)
if pts <= 0:
messagebox.showinfo("Keine Punkte", "Keine Punkte diese Woche.")
return
amount = round(pts * 0.20, 2)
if messagebox.askyesno("Auszahlung", f"{amount:.2f} € auszahlen?"):
date = datetime.now().strftime("%Y-%m-%d")
self.data.setdefault("payout_history", []).append({"date": date, "points": pts, "amount": amount})
self.data["total_earned"] = round(self.data.get("total_earned", 0.0) + amount, 2)
self.data["current_week_points"] = 0
self.data["last_payout_date"] = date
save_data(self.data)
self.update_display()
messagebox.showinfo("Ausgezahlt", f"{amount:.2f} € wurden ausgezahlt. Viel Freude!")
def _check_auto_payout_on_startup(self):
# if it's Saturday and not paid today, optionally show a non-blocking prompt
today = datetime.now()
if today.weekday() == 5: # Saturday
last = self.data.get("last_payout_date")
today_str = today.strftime("%Y-%m-%d")
if last != today_str and self.data.get("current_week_points", 0) > 0:
# show a small popup to allow payout
if messagebox.askyesno("Samstag", "Heute ist Samstag — Auszahlung der Woche durchführen?"):
self.trigger_weekly_payout()
# -------------------------
# Stats (Matplotlib embedded), well padded so labels not cut
# -------------------------
def show_stats(self):
win = tk.Toplevel(self.root)
win.title("📈 Statistiken")
win.geometry("940x500")
win.configure(bg=COLORS["bg"])
tk.Label(win, text="📊 Fortschrittsübersicht", bg=COLORS["bg"], fg=COLORS["accent"],
font=(PREFERRED_FONT, 16, "bold")).pack(pady=10)
cont = tk.Frame(win, bg=COLORS["bg"])
cont.pack(fill=tk.BOTH, expand=True, padx=8, pady=8)
canvas = tk.Canvas(cont, bg=COLORS["bg"], highlightthickness=0)
vscroll = ttk.Scrollbar(cont, orient="vertical", command=canvas.yview)
canvas.configure(yscrollcommand=vscroll.set)
vscroll.pack(side=tk.RIGHT, fill=tk.Y)
canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
inner = tk.Frame(canvas, bg=COLORS["bg"])
canvas.create_window((0,0), window=inner, anchor="nw")
inner.bind("<Configure>", lambda e: canvas.configure(scrollregion=canvas.bbox("all")))
bind_canvas_mousewheel(canvas)
# --- last 7 days line chart
days = []
counts = []
for i in range(6, -1, -1):
d = (datetime.now() - timedelta(days=i)).date()
days.append(d)
key = d.strftime("%Y-%m-%d")
counts.append(sum(1 for v in self.data["history"].get(key, {}).values() if v))
fig1 = Figure(figsize=(8.6, 3.0), dpi=100)
ax1 = fig1.add_subplot(111)
ax1.plot(days, counts, marker="o", linewidth=2, color=COLORS["bar1"])
ax1.set_title("Letzte 7 Tage — erledigte Tasks", color=COLORS["accent"], fontsize=12)
ax1.set_ylabel("Tasks")
ax1.set_ylim(0, max(4, max(counts) + 1))
ax1.grid(axis="y", linestyle="--", alpha=0.25)
ax1.xaxis.set_major_formatter(mdates.DateFormatter("%a\n%d.%m"))
fig1.tight_layout(pad=1.2)
canvas1 = FigureCanvasTkAgg(fig1, master=inner)
canvas1.draw()
canvas1.get_tk_widget().pack(pady=12, fill=tk.BOTH, expand=True)
# --- last 6 months bar chart
months = []
mcounts = []
today = datetime.now().date()
for off in range(5, -1, -1):
y = today.year
m = today.month - off
while m <= 0:
m += 12
y -= 1
months.append((y, m))
# count tasks
c = 0
for date_str, rec in self.data["history"].items():
try:
d = datetime.strptime(date_str, "%Y-%m-%d").date()
except Exception:
continue
if d.year == y and d.month == m:
c += sum(1 for v in rec.values() if v)
mcounts.append(c)
month_labels = [datetime(y, m, 1).strftime("%b %Y") for (y, m) in months]
fig2 = Figure(figsize=(8.6, 3.4), dpi=100)
ax2 = fig2.add_subplot(111)
bars = ax2.bar(month_labels, mcounts, color=COLORS["bar2"], alpha=0.95)
ax2.set_title("Letzte 6 Monate — erledigte Tasks (gesamt)", color=COLORS["accent"], fontsize=12)
ax2.set_ylabel("Tasks gesamt")
ax2.grid(axis="y", linestyle="--", alpha=0.25)
# kaomoji above bars
for rect, val in zip(bars, mcounts):
xpos = rect.get_x() + rect.get_width() / 2
ypos = rect.get_height() + 0.4
if val == 0:
k = "🌙(˘˘)"
elif val < 6:
k = "🔮(˘͈ᵕ˘͈)"
elif val < 15:
k = "✨(๑˃ᴗ˂)ﻭ"
else:
k = "🌟\(^▽^)"
ax2.text(xpos, ypos, k, ha="center", va="bottom", fontsize=10)
fig2.tight_layout(pad=1.3)
canvas2 = FigureCanvasTkAgg(fig2, master=inner)
canvas2.draw()
canvas2.get_tk_widget().pack(pady=12, fill=tk.BOTH, expand=True)
# -------------------------
# Run
# -------------------------
if __name__ == "__main__":
main_root = tk.Tk()
# --- HiDPI / schärfere Schrift (Windows) ---
try:
import ctypes
ctypes.windll.shcore.SetProcessDpiAwareness(1) # SYSTEM_AWARE
except Exception:
pass
# --- Tk-Skalierung an System-DPI anpassen ---
dpi = main_root.winfo_fpixels('1i')
main_root.tk.call("tk", "scaling", dpi / 72)
app = TrackerApp(main_root)
main_root.mainloop()