Skip to content

Commit 7917e90

Browse files
authored
Merge pull request #104 from Sakth1/Dev-official
Dev official: - Added logo - Reworked splash screen - Reworked app startup - Added readme - Minor UI improvements
2 parents 64c4159 + 9c1b7b1 commit 7917e90

File tree

6 files changed

+511
-257
lines changed

6 files changed

+511
-257
lines changed

UI/AppStartup.py

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
from PySide6.QtWidgets import QApplication, QMessageBox
2+
from PySide6.QtCore import QThread, Signal, QObject
3+
import os, time
4+
5+
from UI.SplashScreen import SplashScreen
6+
from UI.MainWindow import MainWindow
7+
from utils.Logger import logger
8+
from utils.CheckInternet import Internet
9+
10+
11+
# STARTUP WORKER THREAD
12+
class StartupWorker(QThread):
13+
status_updated = Signal(str, int) # message, progress %
14+
finished = Signal(bool)
15+
step_timing = {}
16+
17+
def __init__(self, parent=None):
18+
super().__init__(parent)
19+
20+
def run(self):
21+
self.step("Checking internet connection...", 10)
22+
internet = Internet()
23+
connected = internet.check_internet()
24+
25+
for _ in range(2):
26+
if connected:
27+
break
28+
time.sleep(1)
29+
connected = internet.check_internet()
30+
31+
if not connected:
32+
self.finished.emit(False)
33+
return
34+
35+
self.step("Initializing plugins...", 25)
36+
time.sleep(0.5)
37+
38+
self.step("Loading UI modules...", 45)
39+
time.sleep(0.5)
40+
41+
self.step("Preparing database engine...", 65)
42+
time.sleep(0.5)
43+
44+
self.step("Optimizing startup cache...", 80)
45+
time.sleep(0.5)
46+
47+
self.step("Finalizing system checks...", 95)
48+
time.sleep(0.4)
49+
50+
self.step("Startup ready", 100)
51+
self.finished.emit(True)
52+
53+
def step(self, msg, progress):
54+
logger.info(msg)
55+
self.status_updated.emit(msg, progress)
56+
self.step_timing[msg] = time.time()
57+
58+
59+
# APP STARTUP CONTROLLER
60+
class AppStartup(QObject):
61+
def __init__(self):
62+
super().__init__()
63+
64+
base_dir = os.path.dirname(os.path.abspath(__file__))
65+
self.base_dir = os.path.dirname(base_dir)
66+
gif_path = os.path.join(self.base_dir, "assets", "splash", "loading.gif")
67+
68+
# parent=None to avoid QDialog parent type error
69+
self.splash = SplashScreen(parent=None, gif_path=gif_path)
70+
self.splash.set_title("StaTube - YouTube Data Analysis Tool")
71+
self.splash.update_status("Booting system...")
72+
self.splash.set_progress(5)
73+
74+
# Use animated show
75+
self.splash.show_with_animation()
76+
77+
logger.info("Splash screen shown with fade-in animation.")
78+
79+
self.start_worker()
80+
81+
# START BACKGROUND TASK
82+
def start_worker(self):
83+
self.worker = StartupWorker()
84+
self.worker.status_updated.connect(self.on_status_update)
85+
self.worker.finished.connect(self.on_finished)
86+
self.worker.start()
87+
88+
def on_status_update(self, message: str, progress: int):
89+
self.splash.update_status(message)
90+
self.splash.set_progress(progress)
91+
92+
# FINAL HANDOFF
93+
def on_finished(self, connected: bool):
94+
self.splash.fade_and_close()
95+
if not connected:
96+
msg = QMessageBox()
97+
msg.setIcon(QMessageBox.Warning)
98+
msg.setWindowTitle("Connection Issue")
99+
msg.setText(
100+
"No internet connection detected.\n\n"
101+
"StaTube can continue in offline mode, but some features may not work."
102+
)
103+
continue_btn = msg.addButton("Continue Offline", QMessageBox.AcceptRole)
104+
quit_btn = msg.addButton("Quit", QMessageBox.RejectRole)
105+
msg.setDefaultButton(continue_btn)
106+
msg.exec()
107+
108+
if msg.clickedButton() == quit_btn:
109+
QApplication.instance().quit()
110+
return
111+
112+
# NOW SAFE TO CREATE MAIN WINDOW
113+
logger.info("Launching MainWindow after verified startup.")
114+
try:
115+
self.main_window = MainWindow()
116+
self.main_window.finish_initialization()
117+
self.main_window.showMaximized()
118+
except Exception as e:
119+
logger.exception("Fatal UI startup failure")
120+
QMessageBox.critical(
121+
None,
122+
"Startup Failure",
123+
"StaTube failed to initialize UI. Starting in Safe Mode."
124+
)
125+
logger.info("===== Startup Timing Report =====")
126+
for step, t in self.worker.step_timing.items():
127+
logger.info(f"{step}")

UI/Homepage.py

Lines changed: 56 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -207,18 +207,27 @@ def on_item_selected(self, item: QListWidgetItem) -> None:
207207

208208
@QtCore.Slot()
209209
def show_search_splash(self) -> None:
210-
"""
211-
Show splash screen for search operation.
212-
213-
Creates and displays a splash screen with an animated loading GIF
214-
to provide visual feedback during channel search operations.
215-
"""
216210
cwd = os.getcwd()
217211
gif_path = os.path.join(cwd, "assets", "gif", "loading.gif")
212+
213+
if self.splash:
214+
self.splash.close()
215+
218216
self.splash = SplashScreen(parent=self.mainwindow, gif_path=gif_path)
219217
self.splash.set_title("Searching Channels...")
220218
self.splash.update_status("Fetching channel information...")
221-
self.splash.show()
219+
self.splash.set_progress(0)
220+
221+
# Runtime overlay + cancel support
222+
self.splash.enable_runtime_mode(
223+
parent_window=self.mainwindow,
224+
cancel_callback=self.cancel_search
225+
)
226+
227+
# THIS IS REQUIRED
228+
self.splash.show_with_animation()
229+
self.splash.raise_()
230+
self.splash.activateWindow()
222231

223232
@QtCore.Slot(int, str)
224233
def on_progress_update(self, progress: int, status: str) -> None:
@@ -240,15 +249,20 @@ def on_progress_update(self, progress: int, status: str) -> None:
240249

241250
@QtCore.Slot()
242251
def close_splash(self) -> None:
243-
"""
244-
Close splash screen.
245-
246-
Closes and cleans up the splash screen instance if it exists.
247-
"""
248252
if self.splash:
249-
self.splash.close()
253+
self.splash.fade_and_close(400)
250254
self.splash = None
251255

256+
def cancel_search(self):
257+
logger.warning("User cancelled search operation.")
258+
259+
self.stop_event.set()
260+
261+
if self.search_thread_instance and self.search_thread_instance.is_alive():
262+
self.search_thread_instance.join(timeout=0.5)
263+
264+
self.close_splash_signal.emit()
265+
252266
def update_results(self, channels: List[str]) -> None:
253267
"""
254268
Update dropdown list with search results.
@@ -382,27 +396,25 @@ def _run_search(self, query: str, final: bool) -> None:
382396

383397
# Check if thread should stop before starting work
384398
if self.stop_event.is_set():
399+
self.close_splash_signal.emit()
385400
logger.debug("Search thread cancelled before execution")
386401
return
387402

388403
try:
389-
if final:
390-
# Show splash screen for final search
391-
self.show_splash_signal.emit()
392-
404+
if final:
393405
# Define progress callback for the search
394-
def progress_callback(progress: Any, status: Optional[str] = None) -> None:
395-
"""
396-
Callback function for reporting search progress.
397-
398-
Args:
399-
progress: Progress value (int/float for percentage, str for status message)
400-
status: Optional status message, defaults to None
401-
"""
406+
def progress_callback(progress: Any, status: Optional[str] = None):
407+
if self.stop_event.is_set():
408+
return
409+
402410
if isinstance(progress, (int, float)):
403411
self.progress_update.emit(int(progress), status or "")
412+
if self.splash:
413+
self.splash.update_eta(int(progress))
414+
404415
elif isinstance(progress, str):
405-
self.progress_update.emit(-1, progress) # -1 means don't update progress bar
416+
self.progress_update.emit(-1, progress)
417+
406418

407419
# Perform search with progress tracking
408420
self.channels = self.search.search_channel(
@@ -444,26 +456,33 @@ def progress_callback(progress: Any, status: Optional[str] = None) -> None:
444456
# Signal that search is complete
445457
self.search_complete.emit()
446458

459+
if self.stop_event.is_set():
460+
self.close_splash_signal.emit()
461+
return
462+
447463
def search_channel(self) -> None:
448464
"""
449465
Handle search button click event.
450-
451-
Initiates a final comprehensive search for the query entered in the search bar.
452-
Cancels any ongoing auto-search operations before starting the new search.
453-
Clears previous incomplete results to ensure fresh data is retrieved.
454466
"""
455467
query = self.searchbar.text().strip()
456468
if not query:
457469
return
458470

459-
# --- Cancel any ongoing auto-search ---
471+
# CANCEL any running search
460472
if self.search_thread_instance and self.search_thread_instance.is_alive():
461473
self.stop_event.set()
462-
self.search_thread_instance.join(timeout=1.0) # Ensure it fully stops before continuing
474+
self.search_thread_instance.join(timeout=0.5)
463475

464-
# Clear old incomplete results
465476
self.channels = None
466-
self.search.search_channel(name=None) # reset internal state if needed
467-
468-
# --- Now run the final search with fresh data ---
469-
self.search_keyword(query=query, final=True)
477+
self.stop_event.clear()
478+
479+
# SHOW SPLASH IMMEDIATELY (MAIN THREAD)
480+
self.show_search_splash()
481+
482+
# START WORKER THREAD AFTER SPLASH IS VISIBLE
483+
self.search_thread_instance = threading.Thread(
484+
target=self._run_search,
485+
daemon=True,
486+
args=(query, True)
487+
)
488+
self.search_thread_instance.start()

0 commit comments

Comments
 (0)