diff --git a/classes/app.py b/classes/app.py index 9f41f7b..44cf9d5 100644 --- a/classes/app.py +++ b/classes/app.py @@ -4,11 +4,14 @@ from tkinter import NW, E, N, S, W, messagebox, ttk import requests import sv_ttk from ttkthemes import ThemedTk +from classes.custom.themed_frame import ThemedFrame -from classes.frames import FrameDevices, FrameSaves, FrameSettings -from classes.window_config import WindowConfig +from classes.frames.devices import FrameDevices +from classes.frames.saves import FrameSaves +from classes.frames.settings import FrameSettings +from classes.frames.errors import FrameErrorConnection, FrameErrorSavesFolder, FrameErrorUnconfigured from modules.theme_titlebar import theme_title_bar -from modules.utils import configGet, use_dark_mode +from modules.utils import configGet, get_string_mode, use_dark_mode class App(ThemedTk): @@ -28,82 +31,37 @@ class App(ThemedTk): self.title("Stardew Sync") self.geometry(f'{self.window_width}x{self.window_height}+{self.center_x}+{self.center_y}') - self.iconbitmap(path.join("assets", "favicon.ico")) - self.focus_set() + + self.resizable(False, True) sv_ttk.init_theme(self) if use_dark_mode(): - self.configure(background="#1c1c1c") theme_title_bar(self, mode="dark") - sv_ttk.set_theme("dark") - else: - sv_ttk.set_theme("light") + self.update() + + + self.iconbitmap(path.join("assets", "favicon.ico")) + self.focus_set() self.draw_main() - def try_connecting(self): - try: - requests.get(configGet("address")+"/apikey", headers={"apikey": configGet("apikey")}) - # messagebox.showinfo(title="Connection succeeded", message="Server is reachable, apikey is valid, so your client is now ready to be used!") - self.destroy_everything() - except: - messagebox.showerror(title="Configuration error", message="Your client configuration is incorrect. Set it up and try again.") - self.destroy_everything() - - - def verify_connecting(self): - try: - if requests.get(configGet("address")+"/check").status_code == 200: - return - else: - raise requests.exceptions.HTTPError() - except: - self.verify_answer = messagebox.askyesno(title="Connection failed", message="You might have no connection to the server or your api key could expire. Reconfigure the client?\n\nYes to reconfigure and No to retry connecting") - if self.verify_answer is True: - window = WindowConfig(self) - window.grab_set() - if hasattr(self, "setup_button"): - self.setup_button.destroy() - if hasattr(self, "setup_label"): - self.setup_label.destroy() - ttk.Label(self, text="Client configured, try to connect").pack(expand=True) - ttk.Button(self, text="Connect", style="Accent.TButton", width=10, command=self.try_connecting).pack(expand=True) - return False - else: - self.destroy_everything() - return True - - def verify_authorization(self): try: - if requests.get(configGet("address")+"/apikey", headers={"apikey": configGet("apikey")}).status_code == 200: - return + if requests.get(configGet("address")+"/apikey", headers={"apikey": configGet("apikey")}, verify=not configGet("allow_self_signed")).status_code == 200: + return True else: - self.verify_answer = messagebox.askyesno(title="Authorization failed", message="Your apikey could have expired or is invalid. Reconfigure the client?\n\nYes to reconfigure and No to retry connecting") - if self.verify_answer is True: - window = WindowConfig(self) - window.grab_set() - if hasattr(self, "setup_button"): - self.setup_button.destroy() - if hasattr(self, "setup_label"): - self.setup_label.destroy() - ttk.Label(self, text="Client configured, try to connect").pack(expand=True) - ttk.Button(self, text="Connect", style="Accent.TButton", width=10, command=self.try_connecting).pack(expand=True) - return False - else: - self.destroy_everything() - return True + return False except: - self.verify_answer = messagebox.askyesno(title="Connection failed", message="You might have no connection to the server or your api key could expire. Reconfigure the client?\n\nYes to reconfigure and No to retry connecting") - if self.verify_answer is True: - window = WindowConfig(self) - window.grab_set() - ttk.Label(self, text="Client configured, try to connect").pack(expand=True) - ttk.Button(self, text="Connect", style="Accent.TButton", width=10, command=self.try_connecting).pack(expand=True) - else: - self.destroy_everything() + return False + + def verify_saves_dir(self): + + if configGet("saves_location") is None or path.exists(configGet("saves_location")) is False: + return False + else: + return True def frame_saves(self): @@ -124,20 +82,19 @@ class App(ThemedTk): def draw_main(self): - if configGet("address") is None or configGet("apikey") is None: - self.setup_label = ttk.Label(self, text="Your client seems to be unconfigured") - self.setup_label.pack(expand=True) - self.setup_button = ttk.Button(self, text="Set up", style="Accent.TButton", width=10, command=self.open_window_config) - self.setup_button.pack(expand=True) + sv_ttk.set_theme(get_string_mode()) - if self.verify_connecting() is False: - return + # self.grid_rowconfigure(1, weight=2) + # self.grid_rowconfigure(0, weight=2) + # self.grid_columnconfigure(1, weight=1) + # self.setup_label = ttk.Label(self, text="Your client seems to be unconfigured") + # self.setup_label.grid(column=1, row=0, padx=9, pady=9, sticky=S) + # self.setup_button = ttk.Button(self, text="Set up", style="Accent.TButton", width=10, command=self.open_window_config) + # self.setup_button.grid(column=1, row=1, padx=9, pady=9, sticky=N) + # return - if self.verify_authorization() is False: - return - - self.frame_sidebar = ttk.Frame(self) - self.frame_sidebar.grid(column=0, row=0, sticky=NW) + self.frame_sidebar = ThemedFrame(self) + self.frame_sidebar.grid(column=0, row=0, columnspan=1, sticky=N+S+W+E) self.item_saves = ttk.Button(self.frame_sidebar, text="Saves", width=10, command=self.frame_saves) self.item_saves.grid(column=0, row=0, sticky=W, padx=9, pady=9) @@ -147,23 +104,47 @@ class App(ThemedTk): self.item_settings = ttk.Button(self.frame_sidebar, text="Settings", width=10, command=self.frame_settings) self.item_settings.grid(column=0, row=2, sticky=W+S, padx=9, pady=9) + + if configGet("address") in ["", None] or configGet("apikey") in ["", None]: + self.item_saves.state(["disabled"]) + self.item_devices.state(["disabled"]) + self.connection_error = FrameErrorUnconfigured(self) + self.connection_error.grid(column=1, row=0, rowspan=2, sticky=N+S+W+E) + # messagebox.showerror(title="Configuration error", message="Your client is not properly configured.") + return - self.item_test = ttk.Button(self.frame_sidebar, text="Test", width=10, command=lambda:sv_ttk.set_theme("light")) - self.item_test.grid(column=0, row=3, sticky=W+S, padx=9, pady=9) + if self.verify_authorization() is False: + self.item_saves.state(["disabled"]) + self.item_devices.state(["disabled"]) + self.connection_error = FrameErrorConnection(self) + self.connection_error.grid(column=1, row=0, rowspan=2, sticky=N+S+W+E) + messagebox.showerror(title="Authentication error", message="Your API key seems to be invalid.") + return + + if self.verify_saves_dir() is False: + self.item_saves.state(["disabled"]) + self.connection_error = FrameErrorSavesFolder(self) + self.connection_error.grid(column=1, row=0, rowspan=2, sticky=N+S+W+E) + messagebox.showerror(title="Configuration error", message="Saves folder seems to be invalid.") + return self.frame_saves() - def destroy_everything(self): + def destroy_everything(self, draw=True): for widget in self.winfo_children(): widget.destroy() - self.draw_main() + if draw is True: + self.draw_main() - def open_window_config(self): - window = WindowConfig(self) - window.grab_set() - self.setup_button.destroy() - self.setup_label.destroy() - ttk.Label(self, text="Client configured, try to connect").pack(expand=True) - ttk.Button(self, text="Connect", style="Accent.TButton", width=10, command=self.try_connecting).pack(expand=True) \ No newline at end of file + # def open_window_config(self): + # window = WindowConfig(self) + # window.grab_set() + # self.setup_button.destroy() + # self.setup_label.destroy() + # self.grid_rowconfigure(1, weight=2) + # self.grid_rowconfigure(0, weight=2) + # self.grid_columnconfigure(1, weight=1) + # ttk.Label(self, text="Client configured, try to connect").grid(column=1, row=0, padx=9, pady=9, sticky=S) + # ttk.Button(self, text="Connect", style="Accent.TButton", width=10, command=self.try_connecting).grid(column=1, row=1, padx=9, pady=9, sticky=N) \ No newline at end of file diff --git a/classes/custom/themed_frame.py b/classes/custom/themed_frame.py new file mode 100644 index 0000000..e951a41 --- /dev/null +++ b/classes/custom/themed_frame.py @@ -0,0 +1,14 @@ +from ttkthemes import ThemedTk +from typing import Union +import sv_ttk +from tkinter.ttk import Frame + +from modules.utils import get_string_mode + +class ThemedFrame(Frame): + + def __init__(self, master: ThemedTk, **kwargs) -> None: + + super().__init__(master, **kwargs) + + sv_ttk.set_theme(get_string_mode()) \ No newline at end of file diff --git a/classes/frames.py b/classes/frames.py deleted file mode 100644 index e5e92c0..0000000 --- a/classes/frames.py +++ /dev/null @@ -1,57 +0,0 @@ -from tkinter import EW, NS, ttk - -from ttkthemes import ThemedTk - - -class FrameSaves(ttk.Frame): - - def __init__(self, master: ThemedTk, **kwargs) -> None: - - super().__init__(master, **kwargs) - - master.title("Saves - Stardew Sync") - - self["borderwidth"] = 2 - self["relief"] = "solid" - - for btn in range(0, 10): - ttk.Button(self, text="SAVES "+str(btn)).grid(column=0, row=btn, padx=9, pady=9, sticky=EW) - - self.scrollbar = ttk.Scrollbar(self, orient="vertical") - self.scrollbar.grid(column=1, row=0, sticky=NS) - - -class FrameDevices(ttk.Frame): - - def __init__(self, master: ThemedTk, **kwargs) -> None: - - super().__init__(master, **kwargs) - - master.title("Devices - Stardew Sync") - - self["borderwidth"] = 2 - self["relief"] = "solid" - - for btn in range(0, 10): - ttk.Button(self, text="DEVICES "+str(btn)).grid(column=0, row=btn, padx=9, pady=9, sticky=EW) - - self.scrollbar = ttk.Scrollbar(self, orient="vertical") - self.scrollbar.grid(column=1, row=0, sticky=NS) - - -class FrameSettings(ttk.Frame): - - def __init__(self, master: ThemedTk, **kwargs) -> None: - - super().__init__(master, **kwargs) - - master.title("Settings - Stardew Sync") - - self["borderwidth"] = 2 - self["relief"] = "solid" - - for btn in range(0, 10): - ttk.Button(self, text="SETTINGS "+str(btn)).grid(column=0, row=btn, padx=9, pady=9, sticky=EW) - - self.scrollbar = ttk.Scrollbar(self, orient="vertical") - self.scrollbar.grid(column=1, row=0, sticky=NS) \ No newline at end of file diff --git a/classes/frames/devices.py b/classes/frames/devices.py new file mode 100644 index 0000000..ee892a3 --- /dev/null +++ b/classes/frames/devices.py @@ -0,0 +1,23 @@ +from tkinter import EW, NS, ttk + +from ttkthemes import ThemedTk + +from classes.custom.themed_frame import ThemedFrame + + +class FrameDevices(ThemedFrame): + + def __init__(self, master: ThemedTk, **kwargs) -> None: + + super().__init__(master, **kwargs) + + master.title("Devices - Stardew Sync") + + self["borderwidth"] = 2 + self["relief"] = "solid" + + for btn in range(0, 10): + ttk.Button(self, text="DEVICES "+str(btn)).grid(column=0, row=btn, padx=9, pady=9, sticky=EW) + + self.scrollbar = ttk.Scrollbar(self, orient="vertical") + self.scrollbar.grid(column=1, row=0, sticky=NS) \ No newline at end of file diff --git a/classes/frames/errors.py b/classes/frames/errors.py new file mode 100644 index 0000000..0b47be6 --- /dev/null +++ b/classes/frames/errors.py @@ -0,0 +1,85 @@ +from tkinter import N, S, messagebox, ttk +from typing import Any + +import sv_ttk +import requests +from ttkthemes import ThemedTk +from classes.custom.themed_frame import ThemedFrame + +from modules.utils import configGet, get_string_mode + + +def try_connecting(master: Any): + try: + if requests.get(configGet("address")+"/apikey", headers={"apikey": configGet("apikey")}, verify=not configGet("allow_self_signed")).status_code != 200: + messagebox.showerror(title="Authentication error", message="Your API key seems to be invalid.") + return + # messagebox.showinfo(title="Connection succeeded", message="Server is reachable, apikey is valid, so your client is now ready to be used!") + master.destroy_everything() + except: + messagebox.showerror(title="Configuration error", message="Your client configuration is incorrect.") + # master.destroy_everything() + + +class FrameErrorConnection(ThemedFrame): + + def __init__(self, master: ThemedTk, **kwargs) -> None: + + super().__init__(master, **kwargs) + + self.grid_columnconfigure(0, weight=1) + + self.grid_rowconfigure(0, weight=2) + self.grid_rowconfigure(1, weight=2) + + master.columnconfigure(1, weight=1) + + self.label = ttk.Label(self, text="Connection failed") + self.label.grid(column=0, row=0, padx=9, pady=9, sticky=S) + + self.frame_buttons = ThemedFrame(self) + self.frame_buttons.grid(column=0, row=1, sticky=N) + + self.button_retry = ttk.Button(self.frame_buttons, text="Retry", style="Accent.TButton", width=10, command=lambda:try_connecting(master)) + self.button_settings = ttk.Button(self.frame_buttons, text="Settings", width=10, command=master.frame_settings) + self.button_retry.grid(column=0, row=0, padx=9, pady=9) + self.button_settings.grid(column=1, row=0, padx=9, pady=9) + + +class FrameErrorSavesFolder(ThemedFrame): + + def __init__(self, master: ThemedTk, **kwargs) -> None: + + super().__init__(master, **kwargs) + + self.grid_columnconfigure(0, weight=1) + + self.grid_rowconfigure(0, weight=2) + self.grid_rowconfigure(1, weight=2) + + master.columnconfigure(1, weight=1) + + self.label = ttk.Label(self, text="Invalid saves folder") + self.label.grid(column=0, row=0, padx=9, pady=9, sticky=S) + + self.button_settings = ttk.Button(self, text="Settings", style="Accent.TButton", width=10, command=master.frame_settings) + self.button_settings.grid(column=0, row=1, sticky=N) + +class FrameErrorUnconfigured(ThemedFrame): + + def __init__(self, master: ThemedTk, **kwargs) -> None: + + super().__init__(master, **kwargs) + + self.grid_columnconfigure(0, weight=1) + + self.grid_rowconfigure(0, weight=2) + self.grid_rowconfigure(1, weight=2) + + master.columnconfigure(1, weight=1) + + self.label = ttk.Label(self, text="Client uncofigured") + self.label.grid(column=0, row=0, padx=9, pady=9, sticky=S) + + self.button_settings = ttk.Button(self, text="Settings", style="Accent.TButton", width=10, command=master.frame_settings) + self.button_settings.grid(column=0, row=1, sticky=N) \ No newline at end of file diff --git a/classes/frames/saves.py b/classes/frames/saves.py new file mode 100644 index 0000000..b0c53af --- /dev/null +++ b/classes/frames/saves.py @@ -0,0 +1,25 @@ +from tkinter import EW, NS, ttk + +from ttkthemes import ThemedTk + +from classes.custom.themed_frame import ThemedFrame + + +class FrameSaves(ThemedFrame): + + def __init__(self, master: ThemedTk, **kwargs) -> None: + + super().__init__(master, **kwargs) + + master.title("Saves - Stardew Sync") + + self["borderwidth"] = 2 + self["relief"] = "solid" + + self.inside_frame = ThemedFrame(self) + + for btn in range(0, 10): + ttk.Button(self.inside_frame, text="SAVES "+str(btn)).grid(column=0, row=btn, padx=9, pady=9, sticky=EW) + + self.scrollbar = ttk.Scrollbar(self.inside_frame, orient="vertical") + self.scrollbar.grid(column=1, row=0, sticky=NS) \ No newline at end of file diff --git a/classes/frames/settings.py b/classes/frames/settings.py new file mode 100644 index 0000000..ba0bc86 --- /dev/null +++ b/classes/frames/settings.py @@ -0,0 +1,195 @@ +from os import getenv, path +from tkinter import N, NSEW, S, W, E, END, IntVar, StringVar, filedialog, messagebox, ttk + +from ttkthemes import ThemedTk +from classes.custom.themed_frame import ThemedFrame +from modules.theme_titlebar import theme_title_bar + +from modules.utils import configGet, configSet, get_string_mode, use_dark_mode +from modules.logger import logger +import requests +import sv_ttk + + +class FrameSettings(ThemedFrame): + + def __init__(self, master: ThemedTk, **kwargs) -> None: + + super().__init__(master, **kwargs) + + master.title("Configuration") + + self.columnconfigure(0, weight=1) + self.columnconfigure(1, weight=3) + + master.columnconfigure(1, weight=1) + + # Address + self.address_label = ttk.Label(self, text="Address:") + self.address_label.grid(column=0, row=0, sticky=W, padx=9, pady=9) + + self.address_entry = ttk.Entry(self) + self.address_entry.grid(column=1, row=0, sticky=N+S+E+W, padx=9, pady=9) + if configGet("address") is not None: + self.address_entry.insert(0, configGet("address")) + # ======= + + # API Key + self.apikey_label = ttk.Label(self, text="API key:") + self.apikey_label.grid(column=0, row=1, sticky=W, padx=9, pady=9) + + self.apikey_entry = ttk.Entry(self) + self.apikey_entry.grid(column=1, row=1, sticky=N+S+E+W, padx=9, pady=9) + if configGet("apikey") is not None: + self.apikey_entry.insert(0, configGet("apikey")) + # ======= + + # Self-signed + if configGet("allow_self_signed") is not None: + self.self_signed_check_bool = IntVar(value=int(configGet("allow_self_signed"))) + else: + self.self_signed_check_bool = IntVar() + self.self_signed_check = ttk.Checkbutton(self, text="Allow self-signed certificates", variable=self.self_signed_check_bool) + self.self_signed_check.grid(column=1, row=2, sticky=W, padx=9, pady=9) + # =========== + + # Saves location + self.saves_location_label = ttk.Label(self, text="Saves location:") + self.saves_location_label.grid(column=0, row=3, sticky=W, padx=9, pady=9) + + self.saves_frame = ThemedFrame(self) + self.saves_frame.grid(column=1, row=3, sticky=N+S+E+W, padx=9, pady=9) + self.saves_frame.grid_columnconfigure(0, weight=1) + self.saves_frame.grid_columnconfigure(1, weight=3) + + self.saves_location_entry = ttk.Entry(self.saves_frame, width=36) + self.saves_location_entry.grid(column=0, row=0, sticky=NSEW) + if configGet("saves_location") is not None: + self.saves_location_entry.insert(0, configGet("saves_location")) + + self.saves_location_button = ttk.Button(self.saves_frame, text="Browse", width=6, command=lambda:self.select_location(self.saves_location_entry)) + self.saves_location_button.grid(column=1, row=0, sticky=E) + # ============== + + # Saves location + self.saves_location_label = ttk.Label(self, text="Color theme:") + self.saves_location_label.grid(column=0, row=5, sticky=W, padx=9, pady=9) + + if configGet("dark_mode_auto") is True: + self.default_theme = "Auto " + else: + self.default_theme = "Dark " if configGet("dark_mode") is True else "Light " + + self.chosen_theme = StringVar() + self.themes = ("Auto ", "Light ", "Dark ") + self.saves_location_button = ttk.OptionMenu(self, self.chosen_theme, self.default_theme, *self.themes, direction="below", command=self.change_theme) + self.saves_location_button.grid(column=1, row=5, sticky=W, padx=9, pady=9) + # ============== + + self.buttons_frame = ThemedFrame(self) + self.buttons_frame.grid(column=0, columnspan=2, sticky=NSEW, padx=9, pady=9) + self.buttons_frame.grid_columnconfigure(0, weight=1) + + self.validate_button = ttk.Button(self.buttons_frame, text="Validate", width=11, command=self.validate_configuration) + self.validate_button.grid(column=0, row=0, sticky=E, padx=9) + + self.save_button = ttk.Button(self.buttons_frame, text="Save", style="Accent.TButton", width=11, state="disabled", command=self.save_configuration) + self.save_button.grid(column=1, row=0, sticky=E) + + def change_theme(self, *args): + + if self.chosen_theme.get().strip().lower() == "auto": + self.chosen_theme_real = "dark" if use_dark_mode(no_config=True) is True else "light" + else: + self.chosen_theme_real = self.chosen_theme.get().strip().lower() + theme_title_bar(self.master, self.chosen_theme_real) + + def select_location(self, entry: ttk.Entry): + + if path.exists(path.join(str(getenv("APPDATA")), "Stardew Valley")): + self.path_start = path.join(str(getenv("APPDATA")), "Stardew Valley") + elif path.exists(path.join(path.expanduser("~"), ".config", "Stardew Valley")): + self.path_start = path.join(path.expanduser("~"), ".config", "Stardew Valley") + else: + self.path_start = None + + self.path_dir = filedialog.askdirectory(initialdir=self.path_start, title="Select Stardew Valley Saves folder") + + if self.path_dir != "": + entry.delete(0, END) + entry.insert(0, self.path_dir) + + def save_configuration(self): + + if len(self.address_entry.get()) > 0: + if self.address_entry.get().endswith("/"): + self.address_text = self.address_entry.get()[:-1] + else: + self.address_text = self.address_entry.get() + else: + self.address_text = None + + configSet(["address"], self.address_text) + configSet(["apikey"], self.apikey_entry.get()) + configSet(["allow_self_signed"], bool(self.self_signed_check_bool.get())) + configSet(["saves_location"], self.saves_location_entry.get()) + + if self.chosen_theme.get().strip().lower() == "auto": + configSet(["dark_mode_auto"], True) + else: + configSet(["dark_mode_auto"], False) + if self.chosen_theme.get().strip().lower() == "dark": + configSet(["dark_mode"], True) + else: + configSet(["dark_mode"], False) + + # messagebox.showinfo(title="Configuration saved", message="Your client's configuration has been saved") + + self.save_button.state(["disabled"]) + + self.master.item_saves.state(["!disabled"]) + self.master.item_devices.state(["!disabled"]) + + def validate_configuration(self): + + if len(self.address_entry.get()) > 0: + if self.address_entry.get().endswith("/"): + self.address_text = self.address_entry.get() + self.address_entry.delete(0, END) + self.address_entry.insert(0, self.address_text[:-1]) + + try: + requests.get(self.address_entry.get()+"/check", verify=not bool(self.self_signed_check_bool.get())) + except (requests.exceptions.InvalidURL, requests.exceptions.InvalidSchema, requests.exceptions.MissingSchema) as exp: + logger.error(f"Could not validate '{self.address_entry.get()}' due to {exp}") + messagebox.showerror(title="Invalid address", message="Address entered does not seem to be correct one. Please provide API address starting with http:// or https:// \n\nFor example:\n- https://your-api.com:8043\n- https://your-api.com") + return + except (requests.exceptions.SSLError): + logger.error(f"SSL certificate of '{self.address_entry.get()}' does not seem to be valid or is self-signed") + messagebox.showerror(title="Invalid SSL", message="SSL certificate seems to be invalid or self-signed and thus won't be trusted. You can overwrite this by checking 'Allow self-signed certificates'.") + return + except Exception as exp: + logger.error(f"Could not reach '{self.address_entry.get()}' due to {exp}") + messagebox.showerror(title="Connection error", message="Address entered does not seem to be reachable. Please make sure you've entered the correct API address.") + return + + response_apikey = requests.get(self.address_entry.get()+"/apikey", headers={"apikey": self.apikey_entry.get()}, verify=not bool(self.self_signed_check_bool.get())) + + if response_apikey.status_code != 200: + logger.error(f"API key seems to be invalid. API returned {response_apikey.status_code} as a status code") + messagebox.showerror(title="Invalid apikey", message="API key provided does not seem to be valid. Please check for any mistakes and if none found - take a look at the docs to learn how to generate one.") + return + + if not path.exists(self.saves_location_entry.get()): + logger.error(f"Path {self.saves_location_entry.get()} does not seem to exist") + messagebox.showerror(title="Location error", message="Saves folder seems to be invalid. Please provide a valid directory path where Stardew Valley's save files (and folders) are stored.") + return + + configSet(["address"], self.address_entry.get()) + configSet(["apikey"], self.apikey_entry.get()) + configSet(["allow_self_signed"], bool(self.self_signed_check_bool.get())) + configSet(["saves_location"], self.saves_location_entry.get()) + + self.save_button.state(["!disabled"]) + + messagebox.showinfo(title="Configuration completed", message="Your client is now configured and ready to use!") \ No newline at end of file diff --git a/classes/window_config.py b/classes/window_config.py deleted file mode 100644 index 6612e13..0000000 --- a/classes/window_config.py +++ /dev/null @@ -1,112 +0,0 @@ -from os import path -from tkinter import END, E, IntVar, N, S, Toplevel, W, messagebox, ttk - -import requests - -from modules.theme_titlebar import theme_title_bar -from modules.logger import logger -from modules.utils import configGet, configSet, use_dark_mode - - -class WindowConfig(Toplevel): - - def __init__(self, parent): - - super().__init__(parent) - - self.window_width = 430 - self.window_height = 247 - - self.screen_width = self.winfo_screenwidth() - self.screen_height = self.winfo_screenheight() - - self.center_x = int(self.screen_width/2 - self.window_width / 2) - self.center_y = int(self.screen_height/2 - self.window_height / 2) - - self.title("Configuration") - self.geometry(f'{self.window_width}x{self.window_height}+{self.center_x}+{self.center_y}') - self.resizable(False, False) - self.iconbitmap(path.join("assets", "favicon.ico")) - self.focus_set() - - if use_dark_mode() is True: - theme_title_bar(self, "dark") - - self.columnconfigure(0, weight=1) - self.columnconfigure(1, weight=3) - - # Address - self.address_label = ttk.Label(self, text="Address:") - self.address_label.grid(column=0, row=0, sticky=W, padx=9, pady=9) - - self.address_entry = ttk.Entry(self) - self.address_entry.grid(column=1, row=0, sticky=N+S+E+W, padx=9, pady=9) - if configGet("address") is not None: - self.address_entry.insert(0, configGet("address")) - # ======= - - # API Key - self.apikey_label = ttk.Label(self, text="API key:") - self.apikey_label.grid(column=0, row=1, sticky=W, padx=9, pady=9) - - self.apikey_entry = ttk.Entry(self) - self.apikey_entry.grid(column=1, row=1, sticky=N+S+E+W, padx=9, pady=9) - if configGet("apikey") is not None: - self.apikey_entry.insert(0, configGet("apikey")) - # ======= - - # Self-signed - if configGet("allow_self_signed") is not None: - self.self_signed_check_bool = IntVar(value=int(configGet("allow_self_signed"))) - else: - self.self_signed_check_bool = IntVar() - self.self_signed_check = ttk.Checkbutton(self, text="Allow self-signed certificates", variable=self.self_signed_check_bool) - self.self_signed_check.grid(column=1, row=2, sticky=W, padx=9, pady=9) - # =========== - - # Saves location - self.saves_location_label = ttk.Label(self, text="Saves location:") - self.saves_location_label.grid(column=0, row=3, sticky=W, padx=9, pady=9) - - self.saves_location_button = ttk.Button(self, text="Browse", width=6) - self.saves_location_button.grid(column=1, row=3, sticky=W, padx=9, pady=9) - # ============== - self.login_button = ttk.Button(self, text="Log in", style="Accent.TButton", width=10, command=self.validate_configuration) - self.login_button.grid(column=1, row=4, sticky=E, padx=9, pady=9) - - def validate_configuration(self): - - if len(self.address_entry.get()) > 0: - if self.address_entry.get().endswith("/"): - self.address_text = self.address_entry.get() - self.address_entry.delete(0, END) - self.address_entry.insert(0, self.address_text[:-1]) - - try: - requests.get(self.address_entry.get()+"/check", verify=not bool(self.self_signed_check_bool.get())) - except (requests.exceptions.InvalidURL, requests.exceptions.InvalidSchema, requests.exceptions.MissingSchema) as exp: - logger.error(f"Could not validate '{self.address_entry.get()}' due to {exp}") - messagebox.showerror(title="Invalid address", message="Address entered does not seem to be correct one. Please provide API address starting with http:// or https:// \n\nFor example:\n- https://your-api.com:8043\n- https://your-api.com") - return - except (requests.exceptions.SSLError): - logger.error(f"SSL certificate of '{self.address_entry.get()}' does not seem to be valid or is self-signed") - messagebox.showerror(title="Invalid SSL", message="SSL certificate seems to be invalid or self-signed and thus won't be trusted. You can overwrite this by checking 'Allow self-signed certificates'.") - return - except Exception as exp: - logger.error(f"Could not reach '{self.address_entry.get()}' due to {exp}") - messagebox.showerror(title="Connection error", message="Address entered does not seem to be reachable. Please make sure you've entered the correct API address.") - return - - response_apikey = requests.get(self.address_entry.get()+"/apikey", headers={"apikey": self.apikey_entry.get()}, verify=not bool(self.self_signed_check_bool.get())) - - if response_apikey.status_code != 200: - logger.error(f"API key seems to be invalid. API returned {response_apikey.status_code} as a status code") - messagebox.showerror(title="Invalid apikey", message="API key provided does not seem to be valid. Please check for any mistakes and if none found - take a look at the docs to learn how to generate one.") - return - - configSet(["address"], self.address_entry.get()) - configSet(["apikey"], self.apikey_entry.get()) - configSet(["allow_self_signed"], bool(self.self_signed_check_bool.get())) - - messagebox.showinfo(title="Configuration completed", message="Your client is now configured and ready to use!") - self.destroy() \ No newline at end of file diff --git a/modules/theme_titlebar.py b/modules/theme_titlebar.py index 391c78d..2c54498 100644 --- a/modules/theme_titlebar.py +++ b/modules/theme_titlebar.py @@ -1,5 +1,6 @@ import platform import sys +import sv_ttk from ctypes import byref, c_int, sizeof, windll from distutils.version import StrictVersion as Version from os import system @@ -17,12 +18,16 @@ def theme_title_bar(window: Union[ThemedTk, Toplevel], mode: Literal["dark", "li if mode == "dark": value = 1 + #window.configure(background="#1c1c1c") elif mode == "light": value = 0 + #window.configure(background="#ffffff") else: raise ValueError() try: + + sv_ttk.set_theme(mode) window.update() diff --git a/modules/utils.py b/modules/utils.py index 811813c..9576082 100644 --- a/modules/utils.py +++ b/modules/utils.py @@ -1,5 +1,5 @@ from json import JSONDecodeError, dumps, loads -from typing import Any +from typing import Any, Literal import darkdetect @@ -69,14 +69,26 @@ def configGet(key: str, *args: str) -> Any: this_key = this_key[dict_key] return this_key[key] -def use_dark_mode() -> bool: +def use_dark_mode(no_config=False) -> bool: """Return whether dark mode should be used ### Returns: * `bool`: True if yes and False if no """ + if no_config is True: + return bool(darkdetect.isDark()) + if configGet("dark_mode_auto") is True: return bool(darkdetect.isDark()) else: - return configGet("dark_mode") \ No newline at end of file + return configGet("dark_mode") + +def get_string_mode() -> Literal["dark", "light"]: + """Return whether dark mode is used + + ### Returns: + * `Literal["dark", "light"]` + """ + + return "dark" if use_dark_mode() is True else "light" \ No newline at end of file