Addition Of Files

This commit is contained in:
kiyreload27
2025-12-28 17:05:14 +00:00
commit 79fa803fa2
1047 changed files with 4783 additions and 0 deletions

344
gui/update_dialog.py Normal file
View File

@@ -0,0 +1,344 @@
"""
Update Dialog for UmamusumeCardManager
Provides a modal dialog for the update process.
"""
import tkinter as tk
from tkinter import ttk, messagebox, scrolledtext
import threading
import webbrowser
from typing import Optional, Callable
import sys
import os
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from updater.update_checker import check_for_updates, download_update, apply_update, get_current_version
from gui.theme import (
BG_DARK, BG_DARKEST, BG_MEDIUM, BG_LIGHT, BG_HIGHLIGHT,
ACCENT_PRIMARY, ACCENT_SECONDARY, ACCENT_SUCCESS, ACCENT_ERROR,
TEXT_PRIMARY, TEXT_SECONDARY, TEXT_MUTED,
FONT_HEADER, FONT_SUBHEADER, FONT_BODY, FONT_BODY_BOLD, FONT_SMALL,
create_styled_button
)
class UpdateDialog:
"""Modal dialog for checking and applying updates."""
def __init__(self, parent: tk.Tk, on_close_callback: Optional[Callable] = None):
self.parent = parent
self.on_close_callback = on_close_callback
self.update_info = None
self.download_thread = None
self.is_downloading = False
# Create the dialog window
self.dialog = tk.Toplevel(parent)
self.dialog.title("Check for Updates")
self.dialog.geometry("520x600")
self.dialog.resizable(True, True)
self.dialog.minsize(480, 500)
self.dialog.transient(parent)
self.dialog.grab_set()
# Center on parent
self.center_on_parent()
self.dialog.configure(bg=BG_DARK)
# Set up the UI
self.setup_ui()
# Start checking for updates
self.check_for_updates()
def center_on_parent(self):
"""Center the dialog on the parent window."""
self.dialog.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()
dialog_w = 520
dialog_h = 600
x = parent_x + (parent_w - dialog_w) // 2
y = parent_y + (parent_h - dialog_h) // 2
self.dialog.geometry(f"{dialog_w}x{dialog_h}+{x}+{y}")
def setup_ui(self):
"""Set up the dialog UI."""
# Button frame (Create first to pack at bottom)
self.button_frame = tk.Frame(self.dialog, bg=BG_DARK, pady=20, padx=20)
self.button_frame.pack(side=tk.BOTTOM, fill=tk.X)
# Close button
self.close_button = create_styled_button(
self.button_frame,
text="Close",
command=self.close,
style_type='default'
)
self.close_button.pack(side=tk.RIGHT)
# Update button (hidden initially)
self.update_button = create_styled_button(
self.button_frame,
text="⬇️ Download & Install",
command=self.start_download,
style_type='accent'
)
# We don't pack it yet
# Main container
main_frame = tk.Frame(self.dialog, bg=BG_DARK, padx=25, pady=20)
main_frame.pack(fill=tk.BOTH, expand=True)
# Title
self.title_label = tk.Label(
main_frame,
text="🔄 Checking for Updates...",
font=FONT_HEADER,
bg=BG_DARK,
fg=ACCENT_PRIMARY
)
self.title_label.pack(pady=(0, 10))
# Status message
self.status_label = tk.Label(
main_frame,
text="Connecting to GitHub...",
font=FONT_BODY,
bg=BG_DARK,
fg=TEXT_MUTED,
wraplength=460
)
self.status_label.pack(pady=(0, 10))
# Version info frame
self.version_frame = tk.Frame(main_frame, bg=BG_MEDIUM, padx=15, pady=10)
self.version_frame.pack(fill=tk.X, pady=(0, 15))
self.current_version_label = tk.Label(
self.version_frame,
text=f"Current Version: v{get_current_version()}",
font=FONT_BODY,
bg=BG_MEDIUM,
fg=TEXT_SECONDARY
)
self.current_version_label.pack(anchor='w')
self.new_version_label = tk.Label(
self.version_frame,
text="Latest Version: Checking...",
font=FONT_BODY,
bg=BG_MEDIUM,
fg=TEXT_SECONDARY
)
self.new_version_label.pack(anchor='w')
# Release Notes Area
self.notes_label = tk.Label(
main_frame,
text="What's New:",
font=FONT_BODY_BOLD,
bg=BG_DARK,
fg=TEXT_PRIMARY
)
self.notes_label.pack(anchor='w', pady=(0, 5))
# Text box for release notes
self.notes_text = scrolledtext.ScrolledText(
main_frame,
height=10,
bg=BG_MEDIUM,
fg=TEXT_SECONDARY,
font=FONT_SMALL,
borderwidth=0,
highlightthickness=0,
padx=10,
pady=10
)
self.notes_text.pack(fill=tk.BOTH, expand=True, pady=(0, 15))
self.notes_text.insert(tk.END, "Checking for release notes...")
self.notes_text.config(state=tk.DISABLED)
# Progress bar (hidden initially)
self.progress_frame = tk.Frame(main_frame, bg=BG_DARK)
self.progress_frame.pack(fill=tk.X, pady=(0, 10))
self.progress_label = tk.Label(
self.progress_frame,
text="",
font=FONT_SMALL,
bg=BG_DARK,
fg=TEXT_MUTED
)
self.progress_label.pack(anchor='w', pady=(0, 5))
self.progress_bar = ttk.Progressbar(
self.progress_frame,
mode='indeterminate',
length=460
)
self.progress_bar.pack(fill=tk.X)
self.progress_bar.start(10)
def check_for_updates(self):
"""Check for updates in a background thread."""
def check():
self.update_info = check_for_updates()
self.dialog.after(0, self.update_check_complete)
thread = threading.Thread(target=check, daemon=True)
thread.start()
def update_check_complete(self):
"""Called when the update check is complete."""
self.progress_bar.stop()
self.progress_frame.pack_forget() # Hide progress bar when check is done
# Enable text box to update it
self.notes_text.config(state=tk.NORMAL)
self.notes_text.delete(1.0, tk.END)
if self.update_info:
# Update available!
self.title_label.config(text="🎉 Update Available!")
self.status_label.config(
text="A new version is available.",
fg=ACCENT_SUCCESS
)
self.new_version_label.config(
text=f"Latest Version: {self.update_info['new_version']}",
fg=ACCENT_SUCCESS
)
# Show Release Notes
notes = self.update_info.get('release_notes', 'No release notes available.')
self.notes_text.insert(tk.END, notes)
# Show update button
self.update_button.pack(side=tk.RIGHT, padx=(0, 10))
else:
# Up to date or error
self.title_label.config(text="✅ You're Up to Date!")
self.status_label.config(
text=f"You are running the latest version.",
fg=TEXT_SECONDARY
)
self.new_version_label.config(
text=f"Latest Version: v{get_current_version()}",
fg=ACCENT_SUCCESS
)
self.notes_text.insert(tk.END, "You are using the latest version of Umamusume Support Card Manager.\n\nEnjoy!")
self.notes_text.config(state=tk.DISABLED)
def start_download(self):
"""Start downloading the update."""
if self.is_downloading or not self.update_info:
return
self.is_downloading = True
self.update_button.config(state=tk.DISABLED, text="Downloading...")
self.close_button.config(state=tk.DISABLED)
self.title_label.config(text="⬇️ Downloading Update...")
self.status_label.config(text="Please wait...", fg=TEXT_MUTED)
# Configure progress bar for determinate mode
self.progress_frame.pack(fill=tk.X, pady=(0, 10)) # Show progress frame again
self.progress_bar.config(mode='determinate', maximum=100)
self.progress_bar.pack(fill=tk.X)
self.progress_bar['value'] = 0
def download():
def progress_callback(downloaded, total):
if total > 0:
percent = int((downloaded / total) * 100)
mb_downloaded = downloaded / (1024 * 1024)
mb_total = total / (1024 * 1024)
self.dialog.after(0, lambda: self.update_progress(percent, mb_downloaded, mb_total))
download_path = download_update(self.update_info['download_url'], progress_callback)
self.dialog.after(0, lambda: self.download_complete(download_path))
self.download_thread = threading.Thread(target=download, daemon=True)
self.download_thread.start()
def update_progress(self, percent: int, downloaded_mb: float, total_mb: float):
"""Update the progress bar."""
self.progress_bar['value'] = percent
self.progress_label.config(text=f"Downloaded: {downloaded_mb:.1f} MB / {total_mb:.1f} MB ({percent}%)")
def download_complete(self, download_path: Optional[str]):
"""Called when the download is complete."""
self.is_downloading = False
if download_path:
self.title_label.config(text="✅ Download Complete!")
self.status_label.config(
text="Update ready to install.",
fg=ACCENT_SUCCESS
)
# Change button to install
self.update_button.config(
state=tk.NORMAL,
text="🔄 Install & Restart",
command=lambda: self.install_update(download_path)
)
self.close_button.config(state=tk.NORMAL)
else:
self.title_label.config(text="❌ Download Failed")
self.status_label.config(
text="Failed not download update.",
fg=ACCENT_ERROR
)
self.update_button.config(state=tk.NORMAL, text="⬇️ Retry Download")
self.close_button.config(state=tk.NORMAL)
def install_update(self, download_path: str):
"""Install the downloaded update."""
self.title_label.config(text="🔄 Installing Update...")
self.status_label.config(text="Applying update...", fg=TEXT_MUTED)
self.update_button.config(state=tk.DISABLED)
self.close_button.config(state=tk.DISABLED)
if apply_update(download_path):
# Exit the application - the updater script will restart it
self.dialog.after(1000, lambda: self.parent.quit())
else:
messagebox.showinfo(
"Manual Update Required",
f"The update was downloaded but cannot be applied automatically.\n\n"
f"Downloaded file location:\n{download_path}\n\n"
f"Please replace the current executable manually.",
parent=self.dialog
)
self.close()
def close(self):
"""Close the dialog."""
if self.on_close_callback:
self.on_close_callback()
self.dialog.destroy()
def show_update_dialog(parent: tk.Tk, on_close_callback: Optional[Callable] = None) -> UpdateDialog:
"""
Show the update dialog.
Args:
parent: The parent Tk window
on_close_callback: Optional callback when dialog is closed
Returns:
The UpdateDialog instance
"""
return UpdateDialog(parent, on_close_callback)