Compare commits
33 Commits
4fa2a3ad9a
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
7a2c28d2d9 | ||
|
217c1c4691 | ||
|
ac8b60c775 | ||
|
fba043a052 | ||
|
3bab4faa46 | ||
|
e631d1717e | ||
|
b39a0253d1 | ||
|
3bae2b646a | ||
|
76cbf53789 | ||
|
fb53eec810 | ||
69f26cbb03 | |||
|
d2ca33396e | ||
|
d2066e44cb | ||
b186aea266 | |||
cc69506408 | |||
70b2048788 | |||
|
a37196ab22 | ||
|
437a37f569 | ||
|
cf0c8f8e4c | ||
|
97bea5cd90 | ||
|
76defcea2f | ||
|
c4813d62c8 | ||
|
f075cf6c29 | ||
|
ab25fe7b43 | ||
|
d724ed22b1 | ||
|
8b9119afa9 | ||
|
4f8e60ace2 | ||
|
cd6b6599c5 | ||
|
29d84082f1 | ||
d844d182f6 | |||
062a38ceec | |||
|
bfcdcce11d | ||
b376fc3011 |
13
.gitignore
vendored
13
.gitignore
vendored
@@ -153,7 +153,7 @@ cython_debug/
|
||||
#.idea/
|
||||
|
||||
# ---> VisualStudioCode
|
||||
.vscode/*
|
||||
.vscode/sftp.json
|
||||
!.vscode/settings.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
@@ -165,5 +165,14 @@ cython_debug/
|
||||
# Built Visual Studio Code Extensions
|
||||
*.vsix
|
||||
|
||||
# InstallForge projects
|
||||
*.ifp
|
||||
|
||||
# Project
|
||||
cleanup.*
|
||||
venv
|
||||
buildif
|
||||
buildenv
|
||||
sv_ttk
|
||||
distassets
|
||||
testbinary
|
||||
testpython
|
155
.vscode/tasks.json
vendored
Normal file
155
.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,155 @@
|
||||
{
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
// for the documentation about the tasks.json format
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "Clean up",
|
||||
"detail": "Remove all .pyco and and __pycache__ in the whole project",
|
||||
"type": "shell",
|
||||
"windows": {
|
||||
"command": "./.vscode/tasks/windows/cleanup.bat"
|
||||
},
|
||||
"linux": {
|
||||
"command": "bash ./.vscode/tasks/linux/cleanup.sh"
|
||||
},
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "Clean up everything",
|
||||
"detail": "Remove all .pyco and __pycache__ as well as virtual envs, build and dist dirs",
|
||||
"type": "shell",
|
||||
"windows": {
|
||||
"command": "./.vscode/tasks/windows/cleanup_everything.bat"
|
||||
},
|
||||
"linux": {
|
||||
"command": "bash ./.vscode/tasks/linux/cleanup_everything.sh"
|
||||
},
|
||||
"problemMatcher": [],
|
||||
"dependsOn": ["Clean up"]
|
||||
},
|
||||
{
|
||||
"label": "Run (Python) [Dirty]",
|
||||
"detail": "Remove all cache files and start main.py within venv",
|
||||
"type": "shell",
|
||||
"windows": {
|
||||
"command": "./.vscode/tasks/windows/run_python.bat"
|
||||
},
|
||||
"linux": {
|
||||
"command": "bash ./.vscode/tasks/linux/run_python.sh"
|
||||
},
|
||||
"group": {
|
||||
"kind": "test",
|
||||
"isDefault": true
|
||||
},
|
||||
"problemMatcher": [],
|
||||
"dependsOn": ["Clean up"]
|
||||
},
|
||||
{
|
||||
"label": "Run (Python) [Clean]",
|
||||
"detail": "Needs to be ran before the dirty version can be used. Copy source code from 'src' dir into 'testpython', install requirements and start main.py within venv",
|
||||
"type": "shell",
|
||||
"windows": {
|
||||
"command": "./.vscode/tasks/windows/flush_and_run_python.bat"
|
||||
},
|
||||
"linux": {
|
||||
"command": "bash ./.vscode/tasks/linux/flush_and_run_python.sh"
|
||||
},
|
||||
"group": {
|
||||
"kind": "test",
|
||||
"isDefault": true
|
||||
},
|
||||
"problemMatcher": [],
|
||||
"dependsOn": ["Clean up"]
|
||||
//, "Install requirements"]
|
||||
},
|
||||
{
|
||||
"label": "Run (Binary) [Dirty]",
|
||||
"detail": "Copy compiled binaries from 'dest/%os%' dir into 'testbinary/%os%' and start it",
|
||||
"type": "shell",
|
||||
"windows": {
|
||||
"command": "./.vscode/tasks/windows/run_binary.bat"
|
||||
},
|
||||
"linux": {
|
||||
"command": "bash ./.vscode/tasks/linux/run_binary.sh"
|
||||
},
|
||||
"group": {
|
||||
"kind": "test",
|
||||
"isDefault": false
|
||||
},
|
||||
"problemMatcher": [],
|
||||
"dependsOn": ["Build"]
|
||||
},
|
||||
{
|
||||
"label": "Run (Binary) [Clean]",
|
||||
"detail": "Needs to be ran before the dirty version can be used. Copy compiled binaries from 'dest/%os%' dir into 'testbinary/%os%' and start it",
|
||||
"type": "shell",
|
||||
"windows": {
|
||||
"command": "./.vscode/tasks/windows/flush_and_run_binary.bat"
|
||||
},
|
||||
"linux": {
|
||||
"command": "bash ./.vscode/tasks/linux/flush_and_run_binary.sh"
|
||||
},
|
||||
"group": {
|
||||
"kind": "test",
|
||||
"isDefault": false
|
||||
},
|
||||
"problemMatcher": [],
|
||||
"dependsOn": ["Build"]
|
||||
},
|
||||
{
|
||||
"label": "Configure Setup",
|
||||
"detail": "Configure IF setup file on Windows and .deb package on Linux",
|
||||
"type": "shell",
|
||||
"windows": {
|
||||
"command": "./.vscode/tasks/windows/configure_setup.bat"
|
||||
},
|
||||
"linux": {
|
||||
"command": "echo \"Not implemented\""
|
||||
},
|
||||
"problemMatcher": [],
|
||||
"dependsOn": ["Clean up"]
|
||||
},
|
||||
{
|
||||
"label": "Install build requirements",
|
||||
"detail": "Create buildenv and install the modules to run Pyinstaller's build",
|
||||
"type": "shell",
|
||||
"windows": {
|
||||
"command": "./.vscode/tasks/windows/install_build_requirements.bat"
|
||||
},
|
||||
"linux": {
|
||||
"command": "bash ./.vscode/tasks/linux/install_build_requirements.sh"
|
||||
},
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "Install requirements",
|
||||
"detail": "Create venv and install the modules to run 'src/main.py'",
|
||||
"type": "shell",
|
||||
"windows": {
|
||||
"command": "./.vscode/tasks/windows/install_requirements.bat"
|
||||
},
|
||||
"linux": {
|
||||
"command": "bash ./.vscode/tasks/linux/install_requirements.sh"
|
||||
},
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "Build",
|
||||
"detail": "Configure .spec and run Pyinstaller",
|
||||
"type": "shell",
|
||||
"windows": {
|
||||
"command": "./.vscode/tasks/windows/build.bat"
|
||||
},
|
||||
"linux": {
|
||||
"command": "bash ./.vscode/tasks/linux/build.sh"
|
||||
},
|
||||
"problemMatcher": [],
|
||||
"dependsOn": ["Clean up", "Install build requirements"],
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
5
.vscode/tasks/linux/build.sh
vendored
Normal file
5
.vscode/tasks/linux/build.sh
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
|
||||
rm -rf dist/linux/StardewSync
|
||||
|
||||
source buildenv/bin/activate && pyi-makespec ./src/main.py -n "StardewSync" -p "buildenv" -i "./src/assets/favicon.ico" --add-data "./src/config.json:." --add-data "./src/assets:assets" --add-data "buildenv/lib/python3.9/site-packages/sv_ttk/sv.tcl:sv_ttk/" --add-data "buildenv/lib/python3.9/site-packages/sv_ttk/theme/*:sv_ttk/theme/" --noconsole && pyinstaller "StardewSync.spec" --noconfirm --distpath=dist/linux --workpath=build/linux
|
4
.vscode/tasks/linux/cleanup.sh
vendored
Normal file
4
.vscode/tasks/linux/cleanup.sh
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
|
||||
python3 -Bc "import pathlib; [p.unlink() for p in pathlib.Path('.').rglob('*.py[co]')]"
|
||||
python3 -Bc "import pathlib; [p.rmdir() for p in pathlib.Path('.').rglob('__pycache__')]"
|
6
.vscode/tasks/linux/cleanup_everything.sh
vendored
Normal file
6
.vscode/tasks/linux/cleanup_everything.sh
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
rm -rf ./build
|
||||
rm -rf ./dist/linux
|
||||
rm -rf ./venv
|
||||
rm -rf ./buildenv
|
3
.vscode/tasks/linux/configure_setup.sh
vendored
Normal file
3
.vscode/tasks/linux/configure_setup.sh
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
|
||||
# .\dist\InstallForge\install_forge.ifp
|
12
.vscode/tasks/linux/flush_and_run_binary.sh
vendored
Normal file
12
.vscode/tasks/linux/flush_and_run_binary.sh
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
|
||||
rm -rf ./testbinary/linux
|
||||
mkdir ./testbinary/linux
|
||||
|
||||
cp -r ./dist/linux/* ./testbinary/linux/
|
||||
|
||||
cd "testbinary/linux/"
|
||||
|
||||
chmod +x "StardewSync"
|
||||
|
||||
"./StardewSync"
|
8
.vscode/tasks/linux/flush_and_run_python.sh
vendored
Normal file
8
.vscode/tasks/linux/flush_and_run_python.sh
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
#!/bin/bash
|
||||
|
||||
rm -rf testpython
|
||||
mkdir testpython
|
||||
|
||||
cp -r ./src/* ./testpython/
|
||||
|
||||
source venv/bin/activate && cd ./testpython && python ./main.py && deactivate
|
7
.vscode/tasks/linux/install_build_requirements.sh
vendored
Normal file
7
.vscode/tasks/linux/install_build_requirements.sh
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
pip3 install virtualenv
|
||||
|
||||
virtualenv buildenv
|
||||
|
||||
source buildenv/bin/activate && pip install --upgrade Pyinstaller && pip install --upgrade -r ./src/requirements.txt && deactivate
|
7
.vscode/tasks/linux/install_requirements.sh
vendored
Normal file
7
.vscode/tasks/linux/install_requirements.sh
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
pip3 install virtualenv
|
||||
|
||||
virtualenv venv
|
||||
|
||||
source venv/bin/activate && pip install --upgrade -r ./src/requirements.txt && deactivate
|
7
.vscode/tasks/linux/run_binary.sh
vendored
Normal file
7
.vscode/tasks/linux/run_binary.sh
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
cd "testbinary/linux/"
|
||||
|
||||
chmod +x "StardewSync"
|
||||
|
||||
"./StardewSync"
|
3
.vscode/tasks/linux/run_python.sh
vendored
Normal file
3
.vscode/tasks/linux/run_python.sh
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
|
||||
source venv/bin/activate && cd ./testpython && python ./main.py && deactivate
|
3
.vscode/tasks/windows/build.bat
vendored
Normal file
3
.vscode/tasks/windows/build.bat
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
rmdir /S /Q "dist\windows\StardewSync"
|
||||
|
||||
.\buildenv\scripts\activate && pyi-makespec .\src\main.py -n "StardewSync" -p "buildenv" -i "src/assets/favicon.ico" --add-data "src/config.json;." --add-data "src/assets;assets" --add-data "buildenv/Lib/site-packages/sv_ttk/sv.tcl;sv_ttk/" --add-data "buildenv/Lib/site-packages/sv_ttk/theme/*;sv_ttk/theme/" --noconsole && pyinstaller ".\StardewSync.spec" --noconfirm --distpath=dist\windows --workpath=build\windows
|
2
.vscode/tasks/windows/cleanup.bat
vendored
Normal file
2
.vscode/tasks/windows/cleanup.bat
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
python -Bc "import pathlib; [p.unlink() for p in pathlib.Path('.').rglob('*.py[co]')]"
|
||||
python -Bc "import pathlib; [p.rmdir() for p in pathlib.Path('.').rglob('__pycache__')]"
|
4
.vscode/tasks/windows/cleanup_everything.bat
vendored
Normal file
4
.vscode/tasks/windows/cleanup_everything.bat
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
rmdir /S /Q .\build
|
||||
rmdir /S /Q .\dist\windows
|
||||
rmdir /S /Q .\venv
|
||||
rmdir /S /Q .\buildenv
|
1
.vscode/tasks/windows/configure_setup.bat
vendored
Normal file
1
.vscode/tasks/windows/configure_setup.bat
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.\dist\windows\install_forge.ifp
|
7
.vscode/tasks/windows/flush_and_run_binary.bat
vendored
Normal file
7
.vscode/tasks/windows/flush_and_run_binary.bat
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
rmdir /S /Q testbinary\windows
|
||||
mkdir testbinary\windows
|
||||
|
||||
xcopy ".\dist\windows\" ".\testbinary\windows\" /h /i /c /k /e /r /y
|
||||
|
||||
cd ".\testbinary\windows\StardewSync"
|
||||
".\StardewSync.exe"
|
6
.vscode/tasks/windows/flush_and_run_python.bat
vendored
Normal file
6
.vscode/tasks/windows/flush_and_run_python.bat
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
rmdir /S /Q testpython
|
||||
mkdir testpython
|
||||
|
||||
xcopy ".\src\" ".\testpython\" /h /i /c /k /e /r /y
|
||||
|
||||
venv\Scripts\activate && cd .\testpython && python .\main.py && deactivate
|
5
.vscode/tasks/windows/install_build_requirements.bat
vendored
Normal file
5
.vscode/tasks/windows/install_build_requirements.bat
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
pip install virtualenv
|
||||
|
||||
virtualenv buildenv
|
||||
|
||||
buildenv\Scripts\activate && pip install --upgrade Pyinstaller && pip install --upgrade -r .\src\requirements.txt && deactivate
|
5
.vscode/tasks/windows/install_requirements.bat
vendored
Normal file
5
.vscode/tasks/windows/install_requirements.bat
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
pip install virtualenv
|
||||
|
||||
virtualenv venv
|
||||
|
||||
venv\Scripts\activate && pip install --upgrade -r .\src\requirements.txt && deactivate
|
2
.vscode/tasks/windows/run_binary.bat
vendored
Normal file
2
.vscode/tasks/windows/run_binary.bat
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
cd ".\testbinary\windows\StardewSync"
|
||||
".\StardewSync.exe"
|
1
.vscode/tasks/windows/run_python.bat
vendored
Normal file
1
.vscode/tasks/windows/run_python.bat
vendored
Normal file
@@ -0,0 +1 @@
|
||||
venv\Scripts\activate && cd .\testpython && python .\main.py && deactivate
|
Binary file not shown.
Before Width: | Height: | Size: 4.2 KiB |
@@ -1,46 +0,0 @@
|
||||
from datetime import datetime, timezone
|
||||
from tkinter import LEFT, NSEW, Misc, S, W, ttk
|
||||
|
||||
from classes.custom.themed_frame import ThemedFrame
|
||||
from modules.utils import osname
|
||||
|
||||
|
||||
class FrameSave(ThemedFrame):
|
||||
|
||||
def __init__(self, master: Misc, save_dict: str, **kwargs) -> None:
|
||||
|
||||
super().__init__(master, style="Card.TFrame", **kwargs)
|
||||
|
||||
self.widget_width = 47 if osname == "nt" else 42
|
||||
|
||||
self.grid_columnconfigure(0, weight=1)
|
||||
self.grid_columnconfigure(1, weight=3)
|
||||
self.grid_columnconfigure(2, weight=3)
|
||||
|
||||
self.title = ttk.Label(self, text=save_dict["data"]["farmer"], font=("SunValleyBodyStrongFont", 12, "bold"), justify=LEFT, width=self.widget_width)
|
||||
self.title.grid(column=0, row=0, padx=9, pady=9, sticky=W)
|
||||
|
||||
self.description = ttk.Label(self, text=f'Money: {save_dict["data"]["money"]}\nGame version: {save_dict["data"]["game_version"]}\nID: {save_dict["id"]}', width=self.widget_width)
|
||||
self.description.grid(column=0, row=1, padx=9, pady=9, sticky=W)
|
||||
|
||||
self.buttons = ThemedFrame(self)
|
||||
self.buttons.grid(column=0, columnspan=2, row=2, sticky=NSEW, padx=9, pady=9)
|
||||
self.buttons.grid_columnconfigure(0, weight=1)
|
||||
|
||||
upload_date = datetime.utcfromtimestamp(save_dict["date"]).replace(tzinfo=timezone.utc).astimezone(tz=None)
|
||||
self.last_upload = ttk.Label(self.buttons, text=f'{upload_date.strftime("%A, %d %b %Y")}\nUploaded at {upload_date.strftime("%H:%M")} by {save_dict["device"]}', font=("SunValleyBodyFont", 8), justify=LEFT, width=self.widget_width)
|
||||
self.last_upload.grid(column=0, row=0, sticky=W+S)
|
||||
|
||||
# self.button_device_rename_action = partial(self.rename)
|
||||
# self.button_device_rename = ttk.Button(self.buttons, text="Rename", width=11, command=self.button_device_rename_action)
|
||||
# self.button_device_rename.grid(column=0, row=0, padx=9, sticky=E)
|
||||
|
||||
#self.button_device_delete_action = partial(self.delete)
|
||||
self.button_device_delete = ttk.Button(self.buttons, text="Synchronize", style="Accent.TButton", width=11) #, command=self.button_device_delete_action)
|
||||
self.button_device_delete.grid(column=1, row=0, sticky=W)
|
||||
|
||||
def convert_date(self, year: int, month: int, day: int) -> str:
|
||||
pass
|
||||
|
||||
def convert_playtime(self, seconds: int) -> str:
|
||||
pass
|
@@ -1,95 +0,0 @@
|
||||
from os import path, walk
|
||||
from tkinter import E, NSEW, W, ttk
|
||||
import xmltodict
|
||||
|
||||
from ttkthemes import ThemedTk
|
||||
|
||||
from classes.custom.scrollable_frame import ScrollableFrame
|
||||
from classes.custom.themed_frame import ThemedFrame
|
||||
from classes.frames.save import FrameSave
|
||||
from modules.utils import configGet
|
||||
|
||||
|
||||
class FrameSaves(ScrollableFrame):
|
||||
|
||||
def __init__(self, master: ThemedTk, saves: list, **kwargs) -> None:
|
||||
|
||||
super().__init__(master, **kwargs)
|
||||
|
||||
master.title("Saves - Stardew Sync")
|
||||
|
||||
self.saves = saves
|
||||
self.saves_local = []
|
||||
|
||||
# self["borderwidth"] = 1
|
||||
# self["relief"] = "solid"
|
||||
|
||||
for subdir, dirs, files in walk(configGet("saves_location")):
|
||||
try:
|
||||
for dir in dirs:
|
||||
with open(path.join(dir, "SaveGameInfo"), "r", encoding="utf-8") as file:
|
||||
save_dict = xmltodict.parse(file.read())
|
||||
self.saves_local.append(
|
||||
{
|
||||
"id": int(save_dict["SaveGame"]["uniqueIDForThisGame"]),
|
||||
"user": None,
|
||||
"device": configGet("name"),
|
||||
"date": None,
|
||||
"data": {
|
||||
"farmer": save_dict["Farmer"]["name"],
|
||||
"money": int(save_dict["Farmer"]["money"]),
|
||||
"played": int(save_dict["Farmer"]["millisecondsPlayed"]),
|
||||
"save_time": int(save_dict["Farmer"]["saveTime"]),
|
||||
"year": int(save_dict["Farmer"]["yearForSaveGame"]),
|
||||
"season": int(save_dict["Farmer"]["seasonForSaveGame"]),
|
||||
"day": int(save_dict["Farmer"]["dayOfMonthForSaveGame"]),
|
||||
"game_version": save_dict["Farmer"]["gameVersion"]
|
||||
},
|
||||
"file": {
|
||||
"name": None,
|
||||
"uuid": None,
|
||||
"path": None
|
||||
}
|
||||
}
|
||||
)
|
||||
except:
|
||||
pass
|
||||
|
||||
# Merge local and remote saves.
|
||||
# Maybe add something that indicates availability of an
|
||||
# remote update to pull or local new version to push.
|
||||
|
||||
master.columnconfigure(1, weight=1)
|
||||
|
||||
self.render_saves()
|
||||
|
||||
def render_saves(self):
|
||||
|
||||
i = 0
|
||||
for save in self.saves:
|
||||
|
||||
save_frame = FrameSave(self, save_dict=save)
|
||||
save_frame.grid(column=0, row=i, pady=9, padx=9, sticky=W+E)
|
||||
|
||||
i += 1
|
||||
|
||||
if i+1 != len(self.saves):
|
||||
divider = ttk.Separator(self, orient="horizontal")
|
||||
divider.grid(column=0, row=i+1, pady=9)
|
||||
i += 1
|
||||
|
||||
class FrameSavesEmpty(ThemedFrame):
|
||||
|
||||
def __init__(self, master: ThemedTk, **kwargs) -> None:
|
||||
|
||||
super().__init__(master, **kwargs)
|
||||
|
||||
master.title("Saves - Stardew Sync")
|
||||
|
||||
self.grid_columnconfigure(0, weight=1)
|
||||
self.grid_rowconfigure(0, weight=2)
|
||||
|
||||
master.columnconfigure(1, weight=1)
|
||||
|
||||
self.label = ttk.Label(self, text="No saves found")
|
||||
self.label.grid(column=0, row=0, padx=9, pady=9)
|
BIN
src/assets/favicon.ico
Normal file
BIN
src/assets/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 115 KiB |
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 5.7 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
@@ -1,5 +1,6 @@
|
||||
from os import path
|
||||
from tkinter import NSEW, NW, E, N, S, W, PhotoImage, messagebox, ttk
|
||||
from traceback import format_exc
|
||||
|
||||
import requests
|
||||
import sv_ttk
|
||||
@@ -7,13 +8,15 @@ from ttkthemes import ThemedTk
|
||||
|
||||
from classes.custom.scrollable_frame import FIT_HEIGHT, FIT_WIDTH
|
||||
from classes.custom.themed_frame import ThemedFrame
|
||||
from classes.enums import ConnectionState, Theme
|
||||
from classes.frames.devices import FrameDevices, FrameDevicesEmpty
|
||||
from classes.frames.errors import FrameErrorConnection, FrameErrorFirstStart, FrameErrorSavesFolder, FrameErrorUnconfigured
|
||||
from classes.frames.saves import FrameSaves, FrameSavesEmpty
|
||||
from classes.frames.saves import FrameSaves
|
||||
from classes.frames.settings import FrameSettings
|
||||
from classes.toplevel.welcome import ToplevelWelcome
|
||||
from modules.theme_titlebar import theme_title_bar
|
||||
from modules.utils import configGet, get_string_mode, resize_window, set_icon, use_dark_mode
|
||||
from modules.logger import logger
|
||||
|
||||
|
||||
class App(ThemedTk):
|
||||
@@ -33,7 +36,7 @@ class App(ThemedTk):
|
||||
sv_ttk.init_theme(self)
|
||||
|
||||
if use_dark_mode():
|
||||
theme_title_bar(self, mode="dark")
|
||||
theme_title_bar(self, mode=Theme.DARK)
|
||||
self.update()
|
||||
|
||||
set_icon(self)
|
||||
@@ -43,14 +46,11 @@ class App(ThemedTk):
|
||||
self.draw_main()
|
||||
|
||||
|
||||
def verify_authorization(self):
|
||||
def verify_authorization(self) -> ConnectionState:
|
||||
try:
|
||||
if requests.get(configGet("address")+"/apikey", headers={"apikey": configGet("apikey")}, verify=not configGet("allow_self_signed")).status_code == 200:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
return ConnectionState.UNAUTHORIZED if requests.get(configGet("address")+"/apikey", headers={"apikey": configGet("apikey")}, verify=not configGet("allow_self_signed")).status_code == 403 else ConnectionState.OK
|
||||
except:
|
||||
return False
|
||||
return ConnectionState.BAD
|
||||
|
||||
def verify_saves_dir(self):
|
||||
|
||||
@@ -63,18 +63,33 @@ class App(ThemedTk):
|
||||
def frame_saves(self):
|
||||
self.grid_rowconfigure(0, weight=1)
|
||||
self.grid_columnconfigure(1, weight=1)
|
||||
self.frame_saves_saves = requests.get(f'{configGet("address")}/saves?only_ids=True&sort={(configGet("prefer_saves").split())[1]}', headers={"apikey": configGet("apikey")}, verify=not configGet("allow_self_signed"))
|
||||
if self.frame_saves_saves.status_code == 200 and isinstance(self.frame_saves_saves.json(), list) is True and len(self.frame_saves_saves.json()) > 0:
|
||||
self.frame_saves_object = FrameSaves(self, self.frame_saves_saves.json(), vscroll=True)
|
||||
else:
|
||||
self.frame_saves_object = FrameSavesEmpty(self)
|
||||
# try:
|
||||
# self.frame_saves_saves = requests.get(f'{configGet("address")}/saves?only_ids=True&sort={(configGet("prefer_saves").split())[1]}', headers={"apikey": configGet("apikey")}, verify=not configGet("allow_self_signed"))
|
||||
# except Exception as exp:
|
||||
# messagebox.showerror(title="Connection error", message=f"We could not reach the server to check for save entries\n\n{exp}")
|
||||
# logger.error(format_exc())
|
||||
# self.frame_saves_object = FrameErrorConnection(self)
|
||||
# self.frame_saves_object.grid(column=1, row=0, sticky=NSEW)
|
||||
# return
|
||||
# if self.frame_saves_saves.status_code == 200 and isinstance(self.frame_saves_saves.json(), list) is True and len(self.frame_saves_saves.json()) > 0:
|
||||
# self.frame_saves_object = FrameSaves(self, self.frame_saves_saves.json(), vscroll=True)
|
||||
# else:
|
||||
# self.frame_saves_object = FrameSaves(self, [], vscroll=True)
|
||||
self.frame_saves_object = FrameSaves(self, vscroll=True)
|
||||
self.frame_saves_object.grid(column=1, row=0, sticky=NSEW)
|
||||
return
|
||||
|
||||
def frame_devices(self):
|
||||
self.grid_rowconfigure(0, weight=1)
|
||||
self.grid_columnconfigure(1, weight=1)
|
||||
try:
|
||||
self.frame_devices_devices = requests.get(f'{configGet("address")}/devices', headers={"apikey": configGet("apikey")}, verify=not configGet("allow_self_signed"))
|
||||
except Exception as exp:
|
||||
messagebox.showerror(title="Connection error", message=f"We could not reach the server to check the devices list\n\n{exp}")
|
||||
logger.error(format_exc())
|
||||
self.frame_devices_object = FrameErrorConnection(self)
|
||||
self.frame_devices_object.grid(column=1, row=0, sticky=NSEW)
|
||||
return
|
||||
if self.frame_devices_devices.status_code == 200 and isinstance(self.frame_devices_devices.json(), list) is True and len(self.frame_devices_devices.json()) > 0:
|
||||
self.frame_devices_object = FrameDevices(self, self.frame_devices_devices.json(), vscroll=True)
|
||||
else:
|
||||
@@ -136,14 +151,19 @@ class App(ThemedTk):
|
||||
# messagebox.showerror(title="Configuration error", message="Your client is not properly configured.")
|
||||
return
|
||||
|
||||
if self.verify_authorization() is False:
|
||||
self.verified = self.verify_authorization()
|
||||
|
||||
if self.verified in [ConnectionState.BAD, ConnectionState.UNAUTHORIZED]:
|
||||
self.item_saves.state(["disabled"])
|
||||
self.item_devices.state(["disabled"])
|
||||
self.grid_rowconfigure(0, weight=1)
|
||||
self.grid_columnconfigure(1, weight=1)
|
||||
self.connection_error = FrameErrorConnection(self)
|
||||
self.connection_error.grid(column=1, row=0, rowspan=2, sticky=N+S+W+E)
|
||||
if self.verified == ConnectionState.UNAUTHORIZED:
|
||||
messagebox.showerror(title="Authentication error", message="Your API key seems to be invalid.")
|
||||
else:
|
||||
messagebox.showerror(title="Connection error", message="Server is not reachable or your client configuration is incorrect. Please check your network connection and client configuration.")
|
||||
return
|
||||
|
||||
if self.verify_saves_dir() is False:
|
34
src/classes/enums.py
Normal file
34
src/classes/enums.py
Normal file
@@ -0,0 +1,34 @@
|
||||
from enum import Enum
|
||||
|
||||
class SavesPreference(Enum):
|
||||
LATEST_UPLOAD = "latest upload"
|
||||
LATEST_PROGRESS = "latest progress"
|
||||
|
||||
class SavesPreferenceButton(Enum):
|
||||
LATEST_UPLOAD = "Latest upload "
|
||||
LATEST_PROGRESS = "Latest progress "
|
||||
|
||||
class Theme(Enum):
|
||||
AUTO = "auto"
|
||||
LIGHT = "light"
|
||||
DARK = "dark"
|
||||
|
||||
class ThemeButton(Enum):
|
||||
AUTO = "Auto "
|
||||
LIGHT = "Light "
|
||||
DARK = "Dark "
|
||||
|
||||
class ConnectionState(Enum):
|
||||
OK = "ok"
|
||||
BAD = "bad"
|
||||
UNAUTHORIZED = "unauthorized"
|
||||
|
||||
class SaveType(Enum):
|
||||
LOCAL = "local"
|
||||
REMOTE = "remote"
|
||||
BOTH = "both"
|
||||
|
||||
class SaveState(Enum):
|
||||
OUTDATED = "outdated"
|
||||
RECENT = "recent"
|
||||
CURRENT = "current"
|
@@ -11,13 +11,13 @@ 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:
|
||||
if requests.get(configGet("address")+"/apikey", headers={"apikey": configGet("apikey")}, verify=not configGet("allow_self_signed")).status_code == 403:
|
||||
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.")
|
||||
messagebox.showerror(title="Connection error", message="Server is not reachable or your client configuration is incorrect. Please check your network connection and client configuration.")
|
||||
# master.destroy_everything()
|
||||
|
||||
|
124
src/classes/frames/save.py
Normal file
124
src/classes/frames/save.py
Normal file
@@ -0,0 +1,124 @@
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from functools import partial
|
||||
from os import makedirs, path, remove
|
||||
from tkinter import LEFT, NSEW, Misc, S, W, ttk
|
||||
from urllib.parse import urlencode
|
||||
from zipfile import ZipFile
|
||||
|
||||
import requests
|
||||
|
||||
from classes.custom.themed_frame import ThemedFrame
|
||||
from classes.enums import SaveState, SaveType
|
||||
from modules.utils import configGet, osname
|
||||
|
||||
|
||||
class FrameSave(ThemedFrame):
|
||||
|
||||
def __init__(self, master: Misc, save_dict: dict, **kwargs) -> None:
|
||||
|
||||
super().__init__(master, style="Card.TFrame", **kwargs)
|
||||
|
||||
self.widget_width = 47 if osname == "nt" else 42
|
||||
|
||||
self.grid_columnconfigure(0, weight=1)
|
||||
self.grid_columnconfigure(1, weight=3)
|
||||
self.grid_columnconfigure(2, weight=3)
|
||||
|
||||
self.save_dict = save_dict
|
||||
|
||||
self.title = ttk.Label(self, text=f'{save_dict["data"]["farmer"]} ({save_dict["type"].value.upper()}, {save_dict["state"].value.upper()})', font=("SunValleyBodyStrongFont", 12, "bold"), justify=LEFT, width=self.widget_width)
|
||||
self.title.grid(column=0, row=0, padx=9, pady=9, sticky=W)
|
||||
|
||||
self.description = ttk.Label(self, text=f'{self.convert_date(year=save_dict["data"]["year"], season=save_dict["data"]["season"], day=save_dict["data"]["day"])}\n{save_dict["data"]["money"]} Gold, {int((save_dict["data"]["played"]/(1000*60*60))%24)} hours played\nGame version: {save_dict["data"]["game_version"]}', width=self.widget_width)
|
||||
self.description.grid(column=0, row=1, padx=9, pady=9, sticky=W)
|
||||
|
||||
self.buttons = ThemedFrame(self)
|
||||
self.buttons.grid(column=0, columnspan=2, row=2, sticky=NSEW, padx=9, pady=9)
|
||||
self.buttons.grid_columnconfigure(0, weight=1)
|
||||
|
||||
if save_dict["date"] != None:
|
||||
upload_date = datetime.utcfromtimestamp(save_dict["date"]).replace(tzinfo=timezone.utc).astimezone(tz=None)
|
||||
self.last_upload = ttk.Label(self.buttons, text=f'{upload_date.strftime("%A, %d %b %Y")}\nUploaded at {upload_date.strftime("%H:%M")} by {save_dict["device"]}', font=("SunValleyBodyFont", 8), justify=LEFT, width=self.widget_width)
|
||||
else:
|
||||
self.last_upload = ttk.Label(self.buttons, text=f'Exists only locally', font=("SunValleyBodyFont", 8), justify=LEFT, width=self.widget_width)
|
||||
self.last_upload.grid(column=0, row=0, sticky=W+S)
|
||||
|
||||
# self.button_device_rename_action = partial(self.rename)
|
||||
# self.button_device_rename = ttk.Button(self.buttons, text="Rename", width=11, command=self.button_device_rename_action)
|
||||
# self.button_device_rename.grid(column=0, row=0, padx=9, sticky=E)
|
||||
|
||||
self.button_synchronize_action = partial(self.nothing)
|
||||
|
||||
if save_dict["type"] is SaveType.LOCAL:
|
||||
if save_dict["state"] is SaveState.RECENT:
|
||||
self.button_synchronize_action = partial(self.upload, save_dict["id"])
|
||||
print(f'{save_dict["id"]}, local, recent')
|
||||
elif save_dict["type"] is SaveType.REMOTE:
|
||||
if save_dict["state"] is SaveState.RECENT:
|
||||
self.button_synchronize_action = partial(self.download, save_dict["id"], save_dict["date"])
|
||||
print(f'{save_dict["id"]}, remote, recent')
|
||||
else:
|
||||
if save_dict["state"] is SaveState.OUTDATED:
|
||||
self.button_synchronize_action = partial(self.download, save_dict["id"], save_dict["date"])
|
||||
print(f'{save_dict["id"]}, both, outdated')
|
||||
elif save_dict["state"] is SaveState.RECENT:
|
||||
self.button_synchronize_action = partial(self.upload, save_dict["id"])
|
||||
print(f'{save_dict["id"]}, both, recent')
|
||||
|
||||
self.button_synchronize = ttk.Button(self.buttons, text="Synchronize", style="Accent.TButton", width=11, command=self.button_synchronize_action)
|
||||
self.button_synchronize.grid(column=1, row=0, sticky=W)
|
||||
|
||||
if save_dict["state"] is SaveState.CURRENT:
|
||||
self.button_synchronize.state(["disabled"])
|
||||
|
||||
def convert_date(self, year: int, season: int, day: int) -> str:
|
||||
|
||||
if season == 0:
|
||||
season_name = "Spring"
|
||||
elif season == 1:
|
||||
season_name = "Summer"
|
||||
elif season == 2:
|
||||
season_name = "Fall"
|
||||
else:
|
||||
season_name = "Winter"
|
||||
|
||||
return "Day {0} of {1}, Year {2}".format(day, season_name, year)
|
||||
|
||||
def convert_playtime(self, seconds: int) -> str:
|
||||
return ""
|
||||
|
||||
def upload(self, id: int):
|
||||
|
||||
print(f"Upload pressed for {id}")
|
||||
|
||||
files = [("files", open(path.join(configGet("saves_location"), f'{self.save_dict["data"]["farmer"]}_{self.save_dict["id"]}', f'{self.save_dict["data"]["farmer"]}_{self.save_dict["id"]}'), "rb")), ("files", open(path.join(configGet("saves_location"), f'{self.save_dict["data"]["farmer"]}_{self.save_dict["id"]}', "SaveGameInfo"), "rb"))]
|
||||
response = requests.post(f'{configGet("address")}/saves?{urlencode({"device": configGet("name")})}', files=files, headers={"apikey": configGet("apikey")}, verify=not configGet("allow_self_signed"))
|
||||
|
||||
print(response.status_code)
|
||||
|
||||
self.master.render_saves()
|
||||
|
||||
def download(self, id: int, date: int):
|
||||
|
||||
print(f"Download pressed for {id}")
|
||||
|
||||
response = requests.get(f'{configGet("address")}/saves/{id}/{date}/download', headers={"apikey": configGet("apikey")}, verify=not configGet("allow_self_signed"))
|
||||
|
||||
makedirs("tmp", exist_ok=True)
|
||||
|
||||
with open(path.join("tmp", f"{id}.svsave"), "wb") as file:
|
||||
file.write(response.content)
|
||||
|
||||
makedirs(path.join(configGet("saves_location"), f'{self.save_dict["data"]["farmer"]}_{self.save_dict["id"]}'), exist_ok=True)
|
||||
|
||||
with ZipFile(path.join("tmp", f"{id}.svsave"), "r") as file:
|
||||
file.extractall(path.join(configGet("saves_location"), f'{self.save_dict["data"]["farmer"]}_{self.save_dict["id"]}'))
|
||||
|
||||
remove(path.join("tmp", f"{id}.svsave"))
|
||||
|
||||
print(response.status_code)
|
||||
|
||||
self.master.render_saves()
|
||||
|
||||
def nothing(self):
|
||||
pass
|
173
src/classes/frames/saves.py
Normal file
173
src/classes/frames/saves.py
Normal file
@@ -0,0 +1,173 @@
|
||||
from os import path, walk
|
||||
from tkinter import E, NSEW, W, ttk
|
||||
from traceback import print_exc
|
||||
import xmltodict
|
||||
import requests
|
||||
|
||||
from ttkthemes import ThemedTk
|
||||
|
||||
from classes.custom.scrollable_frame import ScrollableFrame
|
||||
from classes.custom.themed_frame import ThemedFrame
|
||||
from classes.enums import SaveState, SaveType
|
||||
from classes.frames.save import FrameSave
|
||||
from modules.utils import configGet
|
||||
|
||||
|
||||
class FrameSaves(ScrollableFrame):
|
||||
|
||||
def __init__(self, master: ThemedTk, **kwargs) -> None:
|
||||
|
||||
super().__init__(master, **kwargs)
|
||||
|
||||
master.title("Saves - Stardew Sync")
|
||||
|
||||
self.saves = self.fetch_saves()
|
||||
|
||||
# self["borderwidth"] = 1
|
||||
# self["relief"] = "solid"
|
||||
|
||||
master.columnconfigure(1, weight=1)
|
||||
|
||||
if len(self.saves) == 0:
|
||||
|
||||
self.grid_columnconfigure(0, weight=1)
|
||||
self.grid_rowconfigure(0, weight=2)
|
||||
|
||||
master.columnconfigure(1, weight=1)
|
||||
|
||||
self.label = ttk.Label(self, text="No saves found")
|
||||
self.label.grid(column=0, row=0, padx=9, pady=9)
|
||||
|
||||
else:
|
||||
|
||||
self.render_saves(refetch=False)
|
||||
|
||||
def fetch_saves_remote(self) -> list:
|
||||
response = self.frame_saves_saves = requests.get(f'{configGet("address")}/saves?only_ids=True&sort={(configGet("prefer_saves").split())[1]}', headers={"apikey": configGet("apikey")}, verify=not configGet("allow_self_signed"))
|
||||
return response.json() if response.status_code == 200 else []
|
||||
|
||||
def fetch_saves(self) -> list:
|
||||
|
||||
self.saves = self.fetch_saves_remote()
|
||||
|
||||
for index, entry in enumerate(self.saves):
|
||||
|
||||
self.saves[index]["state"] = SaveState.RECENT
|
||||
self.saves[index]["type"] = SaveType.REMOTE
|
||||
|
||||
for subdir, dirs, files in walk(configGet("saves_location")):
|
||||
|
||||
try:
|
||||
|
||||
for dir in dirs:
|
||||
|
||||
print("Processing", dir)
|
||||
|
||||
with open(path.join(configGet("saves_location"), dir, "SaveGameInfo"), "r", encoding="utf-8") as file:
|
||||
save_info_dict = xmltodict.parse(file.read())
|
||||
|
||||
with open(path.join(configGet("saves_location"), dir, dir), "r", encoding="utf-8") as file:
|
||||
save_dict = xmltodict.parse(file.read())
|
||||
|
||||
save_date = None
|
||||
save_type = SaveType.LOCAL
|
||||
save_state = SaveState.RECENT
|
||||
|
||||
use_remote = False
|
||||
|
||||
for index, entry in enumerate(self.saves):
|
||||
|
||||
if entry["id"] == int(save_dict["SaveGame"]["uniqueIDForThisGame"]):
|
||||
print(f'Remote: {entry["data"]["save_time"]}; Local: {int(save_info_dict["Farmer"]["saveTime"])}')
|
||||
if int(save_info_dict["Farmer"]["saveTime"]) > entry["data"]["save_time"]:
|
||||
self.saves[index]["type"] = SaveType.BOTH
|
||||
save_type = SaveType.BOTH
|
||||
save_state = SaveState.RECENT
|
||||
save_date = entry["date"]
|
||||
elif int(save_info_dict["Farmer"]["saveTime"]) < entry["data"]["save_time"]:
|
||||
self.saves[index]["type"] = SaveType.BOTH
|
||||
use_remote = True
|
||||
else:
|
||||
self.saves[index]["type"] = SaveType.BOTH
|
||||
self.saves[index]["state"] = SaveState.CURRENT
|
||||
use_remote = True
|
||||
|
||||
if use_remote is True:
|
||||
continue
|
||||
|
||||
if save_state == SaveState.RECENT:
|
||||
|
||||
for index, entry in enumerate(self.saves):
|
||||
if entry["id"] == int(save_dict["SaveGame"]["uniqueIDForThisGame"]):
|
||||
del self.saves[index]
|
||||
break
|
||||
|
||||
self.saves.append(
|
||||
{
|
||||
"id": int(save_dict["SaveGame"]["uniqueIDForThisGame"]),
|
||||
"user": None,
|
||||
"device": configGet("name"),
|
||||
"date": save_date,
|
||||
"type": save_type,
|
||||
"state": SaveState.RECENT,
|
||||
"data": {
|
||||
"farmer": save_info_dict["Farmer"]["name"],
|
||||
"money": int(save_info_dict["Farmer"]["money"]),
|
||||
"played": int(save_info_dict["Farmer"]["millisecondsPlayed"]),
|
||||
"save_time": int(save_info_dict["Farmer"]["saveTime"]),
|
||||
"year": int(save_info_dict["Farmer"]["yearForSaveGame"]),
|
||||
"season": int(save_info_dict["Farmer"]["seasonForSaveGame"]),
|
||||
"day": int(save_info_dict["Farmer"]["dayOfMonthForSaveGame"]),
|
||||
"game_version": save_info_dict["Farmer"]["gameVersion"]
|
||||
},
|
||||
"file": {
|
||||
"name": None,
|
||||
"uuid": None,
|
||||
"path": None
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
except:
|
||||
print_exc()
|
||||
|
||||
return self.saves
|
||||
|
||||
def render_saves(self, refetch: bool = True):
|
||||
|
||||
if refetch is True:
|
||||
self.saves = self.fetch_saves()
|
||||
|
||||
for widget in self.winfo_children():
|
||||
widget.destroy()
|
||||
|
||||
i = 0
|
||||
for save in self.saves:
|
||||
|
||||
# print(save)
|
||||
|
||||
save_frame = FrameSave(self, save_dict=save)
|
||||
save_frame.grid(column=0, row=i, pady=9, padx=9, sticky=W+E)
|
||||
|
||||
i += 1
|
||||
|
||||
if i+1 != len(self.saves):
|
||||
divider = ttk.Separator(self, orient="horizontal")
|
||||
divider.grid(column=0, row=i+1, pady=9)
|
||||
i += 1
|
||||
|
||||
# class FrameSavesEmpty(ThemedFrame):
|
||||
|
||||
# def __init__(self, master: ThemedTk, **kwargs) -> None:
|
||||
|
||||
# super().__init__(master, **kwargs)
|
||||
|
||||
# master.title("Saves - Stardew Sync")
|
||||
|
||||
# self.grid_columnconfigure(0, weight=1)
|
||||
# self.grid_rowconfigure(0, weight=2)
|
||||
|
||||
# master.columnconfigure(1, weight=1)
|
||||
|
||||
# self.label = ttk.Label(self, text="No saves found")
|
||||
# self.label.grid(column=0, row=0, padx=9, pady=9)
|
@@ -4,6 +4,7 @@ from tkinter import N, NSEW, S, W, E, END, IntVar, StringVar, filedialog, messag
|
||||
|
||||
from ttkthemes import ThemedTk
|
||||
from classes.custom.themed_frame import ThemedFrame
|
||||
from classes.enums import SavesPreference, SavesPreferenceButton, Theme
|
||||
from modules.theme_titlebar import theme_title_bar
|
||||
|
||||
from modules.utils import configGet, configSet, get_string_mode, use_dark_mode
|
||||
@@ -92,10 +93,10 @@ class FrameSettings(ThemedFrame):
|
||||
self.saves_preference_label = ttk.Label(self, text="Saves preference:")
|
||||
self.saves_preference_label.grid(column=0, row=4, sticky=W, padx=9, pady=9)
|
||||
|
||||
self.default_preference = "Latest upload " if configGet("prefer_saves") == "latest upload" else "Latest progress "
|
||||
self.default_preference = SavesPreferenceButton.LATEST_UPLOAD.value if configGet("prefer_saves") == SavesPreference.LATEST_UPLOAD.value else SavesPreferenceButton.LATEST_PROGRESS.value
|
||||
|
||||
self.chosen_preference = StringVar()
|
||||
self.preferences = ("Latest upload ", "Latest progress ")
|
||||
self.preferences = (SavesPreferenceButton.LATEST_UPLOAD.value, SavesPreferenceButton.LATEST_PROGRESS.value)
|
||||
self.saves_preference_button = ttk.OptionMenu(self, self.chosen_preference, self.default_preference, *self.preferences, direction="below")
|
||||
self.saves_preference_button.grid(column=1, row=4, sticky=W, padx=9, pady=9)
|
||||
# ================
|
||||
@@ -127,10 +128,13 @@ class FrameSettings(ThemedFrame):
|
||||
|
||||
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"
|
||||
if self.chosen_theme.get().strip().lower() == Theme.AUTO.value:
|
||||
self.chosen_theme_real = Theme.DARK if use_dark_mode(no_config=True) is True else Theme.LIGHT
|
||||
else:
|
||||
self.chosen_theme_real = self.chosen_theme.get().strip().lower()
|
||||
if self.chosen_theme.get().strip().lower() == Theme.LIGHT.value:
|
||||
self.chosen_theme_real = Theme.LIGHT
|
||||
else:
|
||||
self.chosen_theme_real = Theme.DARK
|
||||
theme_title_bar(self.master, self.chosen_theme_real)
|
||||
|
||||
def select_location(self, entry: ttk.Entry):
|
@@ -9,6 +9,7 @@ from classes.custom.image_label import ImageLabel
|
||||
from classes.custom.themed_frame import ThemedFrame
|
||||
|
||||
from classes.custom.themed_toplevel import ThemedToplevel
|
||||
from classes.enums import Theme
|
||||
from modules.logger import logger
|
||||
from modules.theme_titlebar import theme_title_bar
|
||||
from modules.utils import configGet, configSet, resize_window, set_icon, use_dark_mode
|
||||
@@ -31,7 +32,7 @@ class ToplevelWelcome(ThemedToplevel):
|
||||
sv_ttk.init_theme(self)
|
||||
|
||||
if use_dark_mode():
|
||||
theme_title_bar(self, mode="dark")
|
||||
theme_title_bar(self, mode=Theme.DARK)
|
||||
self.update()
|
||||
|
||||
set_icon(self)
|
||||
@@ -233,13 +234,14 @@ class ToplevelWelcome(ThemedToplevel):
|
||||
|
||||
def stage_location_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
|
||||
|
||||
for guess in [ [str(getenv("APPDATA")), "Stardew Valley"], [str(getenv("APPDATA")), "StardewValley"], [path.expanduser("~"), ".config", "Stardew Valley"], [path.expanduser("~"), ".config", "StardewValley"] ]:
|
||||
joined_path = path.join(*guess)
|
||||
if path.exists(joined_path):
|
||||
self.path_start = joined_path
|
||||
break
|
||||
|
||||
self.path_dir = filedialog.askdirectory(initialdir=self.path_start, title="Select Stardew Valley Saves folder")
|
||||
|
||||
if self.path_dir != "":
|
||||
@@ -287,20 +289,23 @@ class ToplevelWelcome(ThemedToplevel):
|
||||
|
||||
def change_theme(self, *args):
|
||||
|
||||
if self.stage_option_menu_var.get().strip().lower() == "auto":
|
||||
self.stage_option_menu_var_real = "dark" if use_dark_mode(no_config=True) is True else "light"
|
||||
if self.stage_option_menu_var.get().strip().lower() == Theme.AUTO.value:
|
||||
self.stage_option_menu_var_real = Theme.DARK if use_dark_mode(no_config=True) is True else Theme.LIGHT
|
||||
else:
|
||||
self.stage_option_menu_var_real = self.stage_option_menu_var.get().strip().lower()
|
||||
if self.stage_option_menu_var.get().strip().lower() == Theme.LIGHT.value:
|
||||
self.stage_option_menu_var_real = Theme.LIGHT
|
||||
else:
|
||||
self.stage_option_menu_var_real = Theme.DARK
|
||||
theme_title_bar(self, self.stage_option_menu_var_real)
|
||||
theme_title_bar(self.master, self.stage_option_menu_var_real)
|
||||
|
||||
def stage_theme_validate(self):
|
||||
|
||||
if self.stage_option_menu_var.get().strip().lower() == "auto":
|
||||
if self.stage_option_menu_var.get().strip().lower() == Theme.AUTO.value:
|
||||
configSet(["dark_mode_auto"], True)
|
||||
else:
|
||||
configSet(["dark_mode_auto"], False)
|
||||
if self.stage_option_menu_var.get().strip().lower() == "dark":
|
||||
if self.stage_option_menu_var.get().strip().lower() == Theme.DARK.value:
|
||||
configSet(["dark_mode"], True)
|
||||
else:
|
||||
configSet(["dark_mode"], False)
|
@@ -6,5 +6,6 @@
|
||||
"saves_location": null,
|
||||
"dark_mode": false,
|
||||
"dark_mode_auto": true,
|
||||
"first_run": true
|
||||
"first_run": true,
|
||||
"prefer_saves": "latest upload"
|
||||
}
|
@@ -3,25 +3,27 @@ import sys
|
||||
import sv_ttk
|
||||
from distutils.version import StrictVersion as Version
|
||||
from os import system
|
||||
from tkinter import Tcl, Toplevel
|
||||
from tkinter import Misc, Tcl, Toplevel
|
||||
from typing import Literal, Union
|
||||
|
||||
from ttkthemes import ThemedTk
|
||||
|
||||
from classes.enums import Theme
|
||||
|
||||
if sys.platform.startswith("win"):
|
||||
from ctypes import byref, c_int, sizeof, windll
|
||||
|
||||
|
||||
def theme_title_bar(window: Union[ThemedTk, Toplevel], mode: Literal["dark", "light"]) -> None:
|
||||
def theme_title_bar(window: Union[ThemedTk, Toplevel, Misc], mode: Literal[Theme.DARK, Theme.LIGHT]) -> None:
|
||||
"""
|
||||
MORE INFO:
|
||||
https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute
|
||||
"""
|
||||
|
||||
if mode == "dark":
|
||||
if mode.value == "dark":
|
||||
value = 1
|
||||
#window.configure(background="#1c1c1c")
|
||||
elif mode == "light":
|
||||
elif mode.value == "light":
|
||||
value = 0
|
||||
#window.configure(background="#ffffff")
|
||||
else:
|
||||
@@ -29,7 +31,7 @@ def theme_title_bar(window: Union[ThemedTk, Toplevel], mode: Literal["dark", "li
|
||||
|
||||
try:
|
||||
|
||||
sv_ttk.set_theme(mode)
|
||||
sv_ttk.set_theme(mode.value)
|
||||
|
||||
window.update()
|
||||
|
Reference in New Issue
Block a user