Files

222 lines
8.1 KiB
Python

import os
import customtkinter as ctk
import requests
import threading
import json
import tkinter as tk
from PIL import Image
class DonorsModal(ctk.CTkToplevel):
def __init__(self, parent, tr):
super().__init__(parent)
self.parent = parent
self.tr = tr # Translation function
self.title(self.tr("Patreons"))
self.geometry("600x600")
self.resizable(False, False)
self.transient(parent)
# Centrar la ventana y crear la UI
self.center_window()
self.create_ui()
self.after_idle(self._safe_grab)
# Start loading data in a separate thread to prevent UI freeze
threading.Thread(target=self._load_donors, daemon=True).start()
def _safe_grab(self):
"""Intenta grab_set hasta que la ventana sea visible."""
try:
self.grab_set()
except tk.TclError:
# Aún no es “viewable”; reintenta en 20 ms
self.after(20, self._safe_grab)
def center_window(self):
"""Centers the modal window relative to the parent window."""
self.update_idletasks()
parent_x = self.parent.winfo_x()
parent_y = self.parent.winfo_y()
parent_w = self.parent.winfo_width()
parent_h = self.parent.winfo_height()
window_w = self.winfo_width()
window_h = self.winfo_height()
cx = parent_x + (parent_w // 2) - (window_w // 2)
cy = parent_y + (parent_h // 2) - (window_h // 2)
self.geometry(f"{window_w}x{window_h}+{cx}+{cy}")
def create_ui(self):
"""Creates the UI elements for the Top Donors modal."""
main_frame = ctk.CTkFrame(self)
main_frame.pack(fill="both", expand=True, padx=20, pady=20)
title_label = ctk.CTkLabel(
main_frame,
text=self.tr("Patreons"),
font=ctk.CTkFont(size=24, weight="bold")
)
title_label.pack(pady=(0, 20))
self.donors_frame = ctk.CTkScrollableFrame(main_frame, fg_color="transparent")
self.donors_frame.pack(fill="both", expand=True, padx=10, pady=(0, 10))
self.grid_container = ctk.CTkFrame(self.donors_frame, fg_color="transparent")
self.grid_container.pack(fill="both", expand=True)
self.status_label = ctk.CTkLabel(
self.grid_container,
text=self.tr("Loading Patreons..."),
font=ctk.CTkFont(size=14),
text_color="gray"
)
self.status_label.grid(pady=20)
close_button = ctk.CTkButton(
main_frame,
text=self.tr("Close"),
command=self.destroy
)
close_button.pack(pady=(10, 0))
def _load_donors(self):
"""Fetches donor data from the API."""
donors = []
error_message = ""
try:
# Explicitly set Accept and User-Agent headers
resp = requests.get(
'https://emydevs.com/coomer/donadores.php',
headers={
'Accept': 'application/json',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36'
},
timeout=10
)
resp.raise_for_status()
donors = resp.json()
except requests.exceptions.RequestException as req_e:
error_message = str(req_e)
print(f"Error fetching donors: {error_message}")
self.after(0, lambda msg=error_message: self.status_label.configure(text=self.tr("Error fetching donors: {error}").format(error=msg), text_color="red"))
return
except json.JSONDecodeError as json_e:
error_message = str(json_e)
print(f"Error decoding JSON: {error_message}")
self.after(0, lambda msg=error_message: self.status_label.configure(text=self.tr("Error processing donor data: {error}").format(error=msg), text_color="red"))
return
# Update the UI on the main thread
self.after(0, lambda: self._show_donors(donors))
def _show_donors(self, donors):
"""Displays donor data in the modal."""
# Clear status label
self.status_label.grid_forget()
# Clear existing data
for widget in self.grid_container.winfo_children():
if widget is self.status_label:
continue
widget.destroy()
columns = 2
for c in range(columns):
self.grid_container.grid_columnconfigure(c, weight=1)
if not donors:
empty = ctk.CTkLabel(self.grid_container, text=self.tr("No donors found."), text_color="gray")
empty.grid(row=0, column=0, columnspan=columns, pady=20)
return
# Sort by amount in descending order, ensuring numeric comparison
def _to_float(v):
try:
return float(v)
except Exception:
return 0.0
donors.sort(key=lambda x: _to_float(x.get("donated_amount", 0)), reverse=True)
# Cache icons once (PNG medals and default)
if not hasattr(self, "_donor_icons"):
icon_dir = os.path.join("resources", "img", "iconos", "donors")
def _load_icon(fname):
path = os.path.join(icon_dir, fname)
if os.path.exists(path):
return ctk.CTkImage(
light_image=Image.open(path),
dark_image=Image.open(path),
size=(20, 20)
)
return None
# Keep a dict for quick access and to hold references
self._donor_icons = {
"gold": _load_icon("gold.png"),
"silver": _load_icon("silver.png"),
"bronze": _load_icon("bronze.png"),
"default": _load_icon("default.png"),
}
# Info note about donors data update
info_label = ctk.CTkLabel(
self.grid_container,
text=self.tr(
"Note: Donor information is updated every 10th of each month.\n"
"Names and donation amounts are retrieved from my Patreon page."
),
font=ctk.CTkFont(size=12, slant="italic"),
text_color="gray",
justify="center",
wraplength=350 # wrap text to fit in ~350px width
)
info_label.grid(row=0, column=0, columnspan=columns, pady=(0, 10), sticky="ew")
for i, donor in enumerate(donors):
donor_name = donor.get("name", self.tr("Unknown Donor"))
# Select icon and colors
icon_key = "default"
font_size = 14
font_weight = "normal"
name_color = "#E0E0E0"
icon_img = self._donor_icons.get(icon_key)
# Row container
donor_row_frame = ctk.CTkFrame(self.grid_container, fg_color="transparent", corner_radius=8)
donor_row_frame.grid_columnconfigure(0, weight=0)
donor_row_frame.grid_columnconfigure(1, weight=1)
row = (i // columns) + 1
col = i % columns
donor_row_frame.grid(row=row, column=col, padx=8, pady=8, sticky="nsew")
# Rank label with icon
rank_label = ctk.CTkLabel(
donor_row_frame,
text="", # remove numeric order
image=icon_img,
compound="left",
font=ctk.CTkFont(size=font_size, weight=font_weight),
width=40,
anchor="w",
text_color=name_color
)
rank_label.grid(row=0, column=0, padx=(5, 5), pady=5, sticky="w")
# Name label
name_label = ctk.CTkLabel(
donor_row_frame,
text=donor_name,
font=ctk.CTkFont(size=font_size, weight=font_weight),
anchor="w",
text_color=name_color
)
name_label.grid(row=0, column=1, padx=(0, 10), pady=5, sticky="ew")
def update_donor_data(self, new_donors):
self._show_donors(new_donors) # Call _show_donors with new data