From 80efa57153d547108fd6f8bc201dd7d28f88dad3 Mon Sep 17 00:00:00 2001 From: Jason Ross Date: Wed, 20 Aug 2025 20:33:38 -0500 Subject: [PATCH] * Elected to go with simple text animation progress symbol for the moment * Unified around one folder browser approach * Added cancellation functionality --- .github/copilot-instructions.md | 6 +- src/adb_manager.py | 30 ++++ src/gui.py | 86 +++------ src/gui/__init__.py | 8 +- .../{windows_browser.py => file_browser.py} | 2 +- src/gui/linux_browser.py | 68 -------- src/gui/main_window.py | 163 ++++++++++++------ src/gui/progress_handler.py | 134 +++++++++----- src/main.py | 22 +-- 9 files changed, 265 insertions(+), 254 deletions(-) rename src/gui/{windows_browser.py => file_browser.py} (99%) delete mode 100644 src/gui/linux_browser.py diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index d5b579d..4b39bbb 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -26,4 +26,8 @@ This is a small Python GUI tool for managing Android device files via ADB (Andro ## UI guidelines -- Application should have a modern and clean design. \ No newline at end of file +- Application should have a modern and clean design. + +## Runtime Instructions +- Always run Python commands through Poetry to ensure the correct environment is used. +- Use `poetry run python src/main.py` to start the application. \ No newline at end of file diff --git a/src/adb_manager.py b/src/adb_manager.py index ff3d1c9..72bd09a 100644 --- a/src/adb_manager.py +++ b/src/adb_manager.py @@ -44,6 +44,7 @@ class ADBManager: def __init__(self): self.progress_callback: Optional[Callable[[int], None]] = None self.status_callback: Optional[Callable[[str], None]] = None + self.current_process: Optional[subprocess.Popen] = None def set_progress_callback(self, callback: Callable[[int], None]): """Set callback function for progress updates.""" @@ -217,6 +218,7 @@ class ADBManager: text=True, bufsize=1, ) + self.current_process = proc line_count = 0 last_progress = 0 @@ -280,6 +282,7 @@ class ADBManager: if proc.returncode == 0: self._update_progress(100) self._update_status("Transfer completed successfully.") + self.current_process = None return True else: # Capture error output for better debugging @@ -292,10 +295,12 @@ class ADBManager: except: pass self._update_status(error_msg) + self.current_process = None return False except Exception as e: self._update_status(f"Transfer error: {e}") + self.current_process = None return False def push_folder(self, local_path: str, remote_path: str) -> bool: @@ -333,6 +338,7 @@ class ADBManager: text=True, bufsize=1, ) + self.current_process = proc except Exception as e: self._update_status(f"Failed to start adb: {e}") return False @@ -399,6 +405,7 @@ class ADBManager: if proc.returncode == 0: self._update_progress(100) self._update_status("Transfer completed successfully.") + self.current_process = None return True else: # Capture error output for better debugging @@ -411,8 +418,31 @@ class ADBManager: except: pass self._update_status(error_msg) + self.current_process = None return False + def cancel_transfer(self) -> bool: + """Cancel the current transfer operation.""" + if self.current_process is not None: + try: + # Terminate the process + self.current_process.terminate() + # Give it a moment to terminate gracefully + try: + self.current_process.wait(timeout=2) + except subprocess.TimeoutExpired: + # Force kill if it doesn't terminate gracefully + self.current_process.kill() + self.current_process.wait() + + self.current_process = None + self._update_status("Transfer cancelled by user") + return True + except Exception as e: + self._update_status(f"Error cancelling transfer: {e}") + return False + return False + class LinuxMTPManager: """Manages MTP operations on Linux systems.""" diff --git a/src/gui.py b/src/gui.py index c0c5750..fe976f0 100644 --- a/src/gui.py +++ b/src/gui.py @@ -14,16 +14,12 @@ try: # Try relative import first (when used as module) from .adb_manager import ( ADBManager, - LinuxMTPManager, - get_platform_type, is_adb_available, ) except ImportError: # Fall back to direct import (when run directly) from adb_manager import ( ADBManager, - LinuxMTPManager, - get_platform_type, is_adb_available, ) @@ -36,25 +32,25 @@ class AndroidFileHandlerGUI(tk.Tk): # Initialize business logic self.adb_manager = ADBManager() - self.adb_manager.set_progress_callback(self.update_progress) - self.adb_manager.set_status_callback(self.set_status) # Transfer tracking for thread safety self.current_transfer_id = 0 - if get_platform_type().startswith("linux"): - self.mtp_manager = LinuxMTPManager() - else: - self.mtp_manager = None - # Setup UI self._setup_ui() + self._setup_progress_handling() self._initialize_app() + def _setup_progress_handling(self): + """Set up progress handling callbacks.""" + # Set up ADB callbacks using our own methods for this legacy version + self.adb_manager.set_progress_callback(self.update_progress) + self.adb_manager.set_status_callback(self.set_status) + def _setup_ui(self): """Setup the user interface.""" # Window configuration - self.title("Android Folder Puller") + self.title("Android File Handler") self.geometry("520x320") self.minsize(520, 320) self.resizable(True, True) @@ -109,7 +105,7 @@ class AndroidFileHandlerGUI(tk.Tk): ).pack(side="right", padx=(5, 0)) # Progress bar - self.progress = ttk.Progressbar(self, orient="horizontal", mode="determinate") + self.progress = ttk.Progressbar(self, orient="horizontal", mode="indeterminate") self.progress.pack(fill="x", padx=10, pady=(20, 5)) # Status label @@ -158,54 +154,11 @@ class AndroidFileHandlerGUI(tk.Tk): self.set_status(f"Device detected: {device}") def browse_remote_folder(self): - """Browse remote Android folders via MTP (Linux)""" - # For Linux - use MTP browsing - if get_platform_type().startswith("linux") and self.mtp_manager: - # First try using existing GVFS mount - gvfs_mount = self.mtp_manager.find_gvfs_mtp_mount() - if gvfs_mount: - try: - folder = filedialog.askdirectory( - initialdir=gvfs_mount, title="Select Android folder" - ) - if folder: - # Convert filesystem path back to Android path - relative_path = os.path.relpath(folder, gvfs_mount) - if relative_path == ".": - android_path = "/sdcard" - else: - android_path = f"/sdcard/{relative_path}".replace("\\", "/") - self.remote_path_var.set(android_path) - return - except Exception as e: - print(f"GVFS browse failed: {e}") - - # Fallback to jmtpfs - mount_point = self.mtp_manager.mount_mtp_device() - if mount_point: - try: - folder = filedialog.askdirectory( - initialdir=mount_point, title="Select Android folder" - ) - if folder: - # Convert filesystem path back to Android path - relative_path = os.path.relpath(folder, mount_point) - if relative_path == ".": - android_path = "/sdcard" - else: - android_path = f"/sdcard/{relative_path}".replace("\\", "/") - self.remote_path_var.set(android_path) - finally: - self.mtp_manager.unmount_mtp_device() - else: - messagebox.showerror( - "Error", - "Could not mount Android device via MTP. " - "Make sure it's connected and set to 'File Transfer' mode.", - ) - else: - # For Windows - show common Android paths dialog - self._show_android_filesystem_tree() + """Browse remote Android folders.""" + messagebox.showinfo( + "Feature Not Available", + "Remote folder browsing is not available in this version. Please manually enter the path." + ) def _show_android_filesystem_tree(self): """Show a browsable Android folder tree for Windows users.""" @@ -717,8 +670,15 @@ class AndroidFileHandlerGUI(tk.Tk): def _update_progress_ui(self, percentage: int): """Internal method to update progress bar on main thread.""" - self.progress["value"] = percentage - self.update_idletasks() + # For indeterminate mode, start/stop animation based on progress + try: + if percentage > 0 and percentage < 100: + self.progress.start(10) # Start animation + elif percentage >= 100: + self.progress.stop() # Stop animation when complete + self.update_idletasks() + except Exception as e: + print(f"[DEBUG] Error updating progress bar: {e}") def set_status(self, message: str): """Update the status label (thread-safe).""" diff --git a/src/gui/__init__.py b/src/gui/__init__.py index f12d5ab..73fa82f 100644 --- a/src/gui/__init__.py +++ b/src/gui/__init__.py @@ -4,14 +4,10 @@ Provides modular GUI components for the Android file transfer application. """ from .main_window import AndroidFileHandlerGUI, main -from .progress_handler import ProgressHandler -from .windows_browser import WindowsAndroidBrowser -from .linux_browser import LinuxAndroidBrowser +from .file_browser import AndroidFileBrowser __all__ = [ "AndroidFileHandlerGUI", "main", - "ProgressHandler", - "WindowsAndroidBrowser", - "LinuxAndroidBrowser", + "AndroidFileBrowser", ] diff --git a/src/gui/windows_browser.py b/src/gui/file_browser.py similarity index 99% rename from src/gui/windows_browser.py rename to src/gui/file_browser.py index 8a9b0bf..e4769f9 100644 --- a/src/gui/windows_browser.py +++ b/src/gui/file_browser.py @@ -8,7 +8,7 @@ import tkinter as tk from tkinter import messagebox, ttk -class WindowsAndroidBrowser: +class AndroidFileBrowser: """Android filesystem browser for Windows using ADB.""" def __init__(self, parent_window, adb_manager, remote_path_var): diff --git a/src/gui/linux_browser.py b/src/gui/linux_browser.py deleted file mode 100644 index bb7339e..0000000 --- a/src/gui/linux_browser.py +++ /dev/null @@ -1,68 +0,0 @@ -""" -Linux Android Browser Module -Provides Android filesystem browsing capabilities for Linux using MTP. -""" - -import os -from tkinter import messagebox, filedialog - - -class LinuxAndroidBrowser: - """Android filesystem browser for Linux using MTP.""" - - def __init__(self, parent_window, mtp_manager, remote_path_var): - self.parent = parent_window - self.mtp_manager = mtp_manager - self.remote_path_var = remote_path_var - - def show_browser(self): - """Browse remote Android folders via MTP (Linux)""" - if not self.mtp_manager: - messagebox.showerror( - "Error", - "MTP manager not available. This feature is only supported on Linux.", - ) - return - - # First try using existing GVFS mount - gvfs_mount = self.mtp_manager.find_gvfs_mtp_mount() - if gvfs_mount: - try: - folder = filedialog.askdirectory( - initialdir=gvfs_mount, title="Select Android folder" - ) - if folder: - # Convert filesystem path back to Android path - relative_path = os.path.relpath(folder, gvfs_mount) - if relative_path == ".": - android_path = "/sdcard" - else: - android_path = f"/sdcard/{relative_path}".replace("\\", "/") - self.remote_path_var.set(android_path) - return - except Exception as e: - print(f"GVFS browse failed: {e}") - - # Fallback to jmtpfs - mount_point = self.mtp_manager.mount_mtp_device() - if mount_point: - try: - folder = filedialog.askdirectory( - initialdir=mount_point, title="Select Android folder" - ) - if folder: - # Convert filesystem path back to Android path - relative_path = os.path.relpath(folder, mount_point) - if relative_path == ".": - android_path = "/sdcard" - else: - android_path = f"/sdcard/{relative_path}".replace("\\", "/") - self.remote_path_var.set(android_path) - finally: - self.mtp_manager.unmount_mtp_device() - else: - messagebox.showerror( - "Error", - "Could not mount Android device via MTP. " - "Make sure it's connected and set to 'File Transfer' mode.", - ) diff --git a/src/gui/main_window.py b/src/gui/main_window.py index a56155d..f2e89db 100644 --- a/src/gui/main_window.py +++ b/src/gui/main_window.py @@ -13,29 +13,21 @@ try: # Try relative import first (when used as module) from ..adb_manager import ( ADBManager, - LinuxMTPManager, - get_platform_type, is_adb_available, ) except ImportError: # Fall back to direct import (when run directly) from adb_manager import ( ADBManager, - LinuxMTPManager, - get_platform_type, is_adb_available, ) try: # Try relative imports first - from .progress_handler import ProgressHandler - from .windows_browser import WindowsAndroidBrowser - from .linux_browser import LinuxAndroidBrowser + from .file_browser import AndroidFileBrowser except ImportError: # Fall back to direct imports - from progress_handler import ProgressHandler - from windows_browser import WindowsAndroidBrowser - from linux_browser import LinuxAndroidBrowser + from src.gui.file_browser import AndroidFileBrowser class AndroidFileHandlerGUI(tk.Tk): @@ -51,11 +43,6 @@ class AndroidFileHandlerGUI(tk.Tk): self.current_transfer_id = 0 self.device_connected = False # Track device connection state - if get_platform_type().startswith("linux"): - self.mtp_manager = LinuxMTPManager() - else: - self.mtp_manager = None - # Setup UI self._setup_ui() self._initialize_components() @@ -64,7 +51,7 @@ class AndroidFileHandlerGUI(tk.Tk): def _setup_ui(self): """Setup the user interface.""" # Window configuration - self.title("Android Folder Puller") + self.title("Android File Handler") self.geometry("520x320") self.minsize(520, 320) self.resizable(True, True) @@ -118,13 +105,9 @@ class AndroidFileHandlerGUI(tk.Tk): local_path_frame, text="Browse...", command=self.browse_local_folder ).pack(side="right", padx=(5, 0)) - # Progress bar - self.progress = ttk.Progressbar(self, orient="horizontal", mode="determinate") - self.progress.pack(fill="x", padx=10, pady=(20, 5)) - # Status label self.status_label = tk.Label(self, text="Status: Idle") - self.status_label.pack(anchor="w", padx=10, fill="x") + self.status_label.pack(anchor="w", padx=10, fill="x", pady=(20, 5)) # Start/Recheck button (will change based on device state) self.start_btn = tk.Button( @@ -137,34 +120,76 @@ class AndroidFileHandlerGUI(tk.Tk): def _initialize_components(self): """Initialize GUI components and handlers.""" - # Progress handler - self.progress_handler = ProgressHandler(self, self.progress, self.status_label) + # Transfer animation state + self.transfer_animation_job = None + self.transfer_dots = 0 + + # Set up ADB callbacks to our own methods + self.adb_manager.set_progress_callback(self._update_progress) + self.adb_manager.set_status_callback(self._update_status) - # Set up ADB callbacks - self.adb_manager.set_progress_callback(self.progress_handler.update_progress) - self.adb_manager.set_status_callback(self.progress_handler.set_status) - - # Browser components - self.windows_browser = WindowsAndroidBrowser( + # Browser component + self.browser = AndroidFileBrowser( self, self.adb_manager, self.remote_path_var ) - self.linux_browser = LinuxAndroidBrowser( - self, self.mtp_manager, self.remote_path_var - ) + + def _update_progress(self, percentage): + """Handle progress updates (simplified).""" + # We ignore the percentage and just rely on status updates + pass + + def _update_status(self, message: str): + """Update status label (thread-safe).""" + # Don't update status if transfer animation is running + if self.transfer_animation_job is not None: + return # Ignore ADB status updates during transfer animation + + def update_ui(): + self.status_label.config(text=message) + self.update_idletasks() + + self.after(0, update_ui) + + def _start_transfer_animation(self): + """Start the 'Transferring...' animation.""" + print("[DEBUG] Starting transfer animation") + self.transfer_dots = 0 + self.transfer_animation_job = True # Mark as active before starting + self._animate_transfer_text() + + def _animate_transfer_text(self): + """Animate the transfer text with dots.""" + if self.transfer_animation_job is not None: + dots = "." * (self.transfer_dots + 1) + status_text = f"Transferring{dots}" + self.status_label.config(text=status_text) + print(f"[DEBUG] Animation update: {status_text}") # Debug output + self.update_idletasks() # Force immediate UI update + self.transfer_dots = (self.transfer_dots + 1) % 5 # Cycle 0-4 dots + # Schedule next update in 500ms + self.transfer_animation_job = self.after(500, self._animate_transfer_text) + + def _stop_transfer_animation(self): + """Stop the transfer animation.""" + print("[DEBUG] Stopping transfer animation") + if self.transfer_animation_job is not None: + if isinstance(self.transfer_animation_job, str): # It's an after job ID + self.after_cancel(self.transfer_animation_job) + self.transfer_animation_job = None def _initialize_app(self): """Initialize the application - check ADB and device.""" # Check adb availability if not is_adb_available(): self.disable_controls() - self.progress_handler.set_status("ADB not found locally. Downloading...") + self._update_status("ADB not found locally. Downloading...") self.update() success = self.adb_manager.download_and_extract_adb() if success: - self.progress_handler.set_status("ADB downloaded and ready.") + self._update_status("ADB downloaded and ready.") self.enable_controls() else: - self.progress_handler.set_status( + self._update_status( "Failed to download ADB. Please check your internet and restart." ) messagebox.showerror("Error", "Failed to download ADB tools. Exiting.") @@ -176,31 +201,26 @@ class AndroidFileHandlerGUI(tk.Tk): def check_device_connection(self): """Check for device connection and update UI accordingly.""" - self.progress_handler.set_status("Checking for connected device...") + self._update_status("Checking for connected device...") self.update() device = self.adb_manager.check_device() if not device: self.device_connected = False self.disable_controls() - self.progress_handler.set_status( + self._update_status( "No device detected. Enable USB debugging and connect your device." ) self._switch_to_recheck_mode() self.show_enable_debugging_instructions() else: self.device_connected = True - self.progress_handler.set_status(f"Device detected: {device}") + self._update_status(f"Device detected: {device}") self._switch_to_transfer_mode() self.enable_controls() def browse_remote_folder(self): """Browse remote Android folders.""" - if get_platform_type().startswith("linux") and self.mtp_manager: - # Use Linux MTP browser - self.linux_browser.show_browser() - else: - # Use Windows ADB browser - self.windows_browser.show_browser() + self.browser.show_browser() def browse_local_folder(self): """Browse for local folder.""" @@ -239,6 +259,12 @@ class AndroidFileHandlerGUI(tk.Tk): text="Start Transfer", command=self.start_transfer, state="normal" ) + def _switch_to_cancel_mode(self): + """Switch button to cancel transfer mode.""" + self.start_btn.config( + text="Cancel Transfer", command=self.cancel_transfer, state="normal" + ) + def handle_button_click(self): """Handle button click - delegates to appropriate method based on device state.""" if self.device_connected: @@ -259,6 +285,36 @@ class AndroidFileHandlerGUI(tk.Tk): """Perform the actual device recheck.""" self.check_device_connection() + def cancel_transfer(self): + """Cancel the current transfer.""" + print("[DEBUG] Cancel transfer requested") + + # Cancel the actual ADB process + cancelled = self.adb_manager.cancel_transfer() + + # Increment transfer ID to invalidate current transfer + self.current_transfer_id += 1 + + # Stop animation and restore UI + self._stop_transfer_animation() + + # Use after() to ensure status update happens after animation stops + def update_cancelled_status(): + if cancelled: + self.status_label.config(text="Transfer cancelled by user.") + else: + self.status_label.config(text="Transfer cancellation failed.") + self.update_idletasks() + + self.after(0, update_cancelled_status) + self.enable_controls() + + # Restore proper button state + if self.device_connected: + self._switch_to_transfer_mode() + else: + self._switch_to_recheck_mode() + def show_enable_debugging_instructions(self): """Show instructions to connect device, enable file transfer, and to enable USB debugging.""" msg = ( @@ -355,21 +411,18 @@ class AndroidFileHandlerGUI(tk.Tk): transfer_id = self.current_transfer_id self.disable_controls() - self.start_btn.config( - state="disabled" - ) # Disable transfer button during transfer - # Reset progress bar in thread-safe way - self.progress_handler.reset_progress() + # Switch button to cancel mode during transfer + self._switch_to_cancel_mode() + # Start the transfer animation + self._start_transfer_animation() if direction == "pull": - self.progress_handler.set_status("Starting pull transfer...") threading.Thread( target=self._pull_thread, args=(remote_path, local_path, transfer_id), daemon=True, ).start() elif direction == "push": - self.progress_handler.set_status("Starting push transfer...") threading.Thread( target=self._push_thread, args=(local_path, remote_path, transfer_id), @@ -388,12 +441,16 @@ class AndroidFileHandlerGUI(tk.Tk): success = self.adb_manager.pull_folder(remote_path, local_path) if success and self.current_transfer_id == transfer_id: + self._stop_transfer_animation() + self._update_status("Transfer completed successfully.") self.show_disable_debugging_reminder() except Exception as e: if self.current_transfer_id == transfer_id: + self._stop_transfer_animation() self.report_error(f"Pull operation failed: {e}") finally: if self.current_transfer_id == transfer_id: + self._stop_transfer_animation() self.enable_controls() # Restore proper button state after transfer self.after(0, self._restore_button_state) @@ -407,12 +464,16 @@ class AndroidFileHandlerGUI(tk.Tk): success = self.adb_manager.push_folder(local_path, remote_path) if success and self.current_transfer_id == transfer_id: + self._stop_transfer_animation() + self._update_status("Transfer completed successfully.") self.show_disable_debugging_reminder() except Exception as e: if self.current_transfer_id == transfer_id: + self._stop_transfer_animation() self.report_error(f"Push operation failed: {e}") finally: if self.current_transfer_id == transfer_id: + self._stop_transfer_animation() self.enable_controls() # Restore proper button state after transfer self.after(0, self._restore_button_state) diff --git a/src/gui/progress_handler.py b/src/gui/progress_handler.py index 2bd5362..feb1497 100644 --- a/src/gui/progress_handler.py +++ b/src/gui/progress_handler.py @@ -11,68 +11,116 @@ class ProgressHandler: """Handles progress bar updates and status messages.""" def __init__( - self, parent_widget, progress_bar: ttk.Progressbar, status_label: tk.Label - ): + self, parent_widget: tk.Widget, progress_bar: ttk.Progressbar, status_label: tk.Label + ) -> None: + """Initialize the progress handler.""" self.parent = parent_widget - self.progress_bar = progress_bar + self.progress_bar = progress_bar # Don't call as function - it's already created self.status_label = status_label + self._last_percentage: float = 0.0 + self._transfer_active: bool = False # Track if a transfer is actually active - def update_progress(self, percentage: int): - """Update the progress bar (thread-safe).""" + def update_progress(self, bytes_transferred_or_percentage, bytes_total=None) -> None: + """Update the progress bar (thread-safe). + + Args: + bytes_transferred_or_percentage: Either bytes transferred (if bytes_total provided) or percentage (0-100) + bytes_total: Total number of bytes to transfer (optional) + """ + if bytes_total is not None: + # Called with bytes_transferred and bytes_total + bytes_transferred = bytes_transferred_or_percentage + if bytes_total > 0: + percentage = (bytes_transferred / bytes_total) * 100.0 + else: + percentage = 0.0 + else: + # Called with just percentage + percentage = float(bytes_transferred_or_percentage) + + # Ensure percentage is within valid range + percentage = max(0.0, min(100.0, percentage)) + # Debug output for large transfer troubleshooting - print(f"[DEBUG] Progress update called: {percentage}%") + if bytes_total is not None: + print(f"[DEBUG] Progress update called: {percentage:.1f}% ({bytes_transferred_or_percentage}/{bytes_total} bytes)") + else: + print(f"[DEBUG] Progress update called: {percentage:.1f}%") # Schedule UI update on main thread using a proper closure - def update_ui(): + def update_ui() -> None: self._update_progress_ui(percentage) self.parent.after(0, update_ui) - def _update_progress_ui(self, percentage: int): - """Internal method to update progress bar on main thread.""" - # Ensure percentage is within valid range - percentage = max(0, min(100, percentage)) - + def _update_progress_ui(self, percentage: float) -> None: + """Internal method to update progress bar on main thread. + + Args: + percentage: Progress percentage (0.0 to 100.0) - used for logging only in indeterminate mode + """ # Debug output for large transfer troubleshooting - current_value = self.progress_bar["value"] - print(f"[DEBUG] UI Progress update: {current_value} -> {percentage}%") + print(f"[DEBUG] Progress update: {percentage:.1f}% (indeterminate mode, transfer_active: {self._transfer_active})") - if ( - hasattr(self, "_last_percentage") - and abs(percentage - self._last_percentage) >= 10 - ): - print( - f"[DEBUG] Major progress jump: {self._last_percentage}% -> {percentage}%" - ) + # Only log significant progress jumps (10% or more) + if abs(percentage - self._last_percentage) >= 10.0: + print(f"[DEBUG] Major progress jump: {self._last_percentage:.1f}% -> {percentage:.1f}%") + self._last_percentage = percentage try: - self.progress_bar["value"] = percentage - self.progress_bar.update() # Force immediate update + # Only start animation if we're in an active transfer and progress > 0 + if self._transfer_active and percentage > 0 and percentage < 100: + self.progress_bar.start(10) # 10ms interval for smooth animation + print(f"[DEBUG] Progress bar animation started") + elif percentage >= 100: + self.progress_bar.stop() # Stop animation when complete + self._transfer_active = False # Transfer is done + print(f"[DEBUG] Progress bar animation stopped (transfer complete)") + self.parent.update_idletasks() - print(f"[DEBUG] Progress bar updated successfully to {percentage}%") - except Exception as e: - print(f"[DEBUG] Error updating progress bar: {e}") + except Exception as exception_error: + print(f"[DEBUG] Error updating progress bar: {exception_error}") - def set_status(self, message: str): - """Update the status label (thread-safe).""" + def reset_progress(self) -> None: + """Reset progress bar to 0 (thread-safe).""" + def update_ui() -> None: + try: + self.progress_bar.stop() # Stop any animation + self._last_percentage = 0.0 + self._transfer_active = False # Not in a transfer + print(f"[DEBUG] Progress bar reset (stopped animation)") + self.parent.update_idletasks() + except Exception as exception_error: + print(f"[DEBUG] Error resetting progress bar: {exception_error}") - # Schedule UI update on main thread using a proper closure - def update_ui(): + self.parent.after(0, update_ui) + + def start_transfer(self) -> None: + """Mark that a transfer is starting (enables progress animation).""" + self._transfer_active = True + print(f"[DEBUG] Transfer marked as active") + + def set_status(self, message: str) -> None: + """Set the status label text (thread-safe). + + Args: + message: The status message to display + """ + # Schedule UI update on main thread + def update_ui() -> None: self._set_status_ui(message) self.parent.after(0, update_ui) - def _set_status_ui(self, message: str): - """Internal method to update status on main thread.""" - self.status_label.config(text=f"Status: {message}") - self.parent.update_idletasks() - - def reset_progress(self): - """Reset progress bar to 0 (thread-safe).""" - - # Schedule UI update on main thread using a proper closure - def update_ui(): - self._update_progress_ui(0) - - self.parent.after(0, update_ui) + def _set_status_ui(self, message: str) -> None: + """Internal method to set status label on main thread. + + Args: + message: The status message to display + """ + try: + self.status_label.config(text=message) + self.parent.update_idletasks() + except Exception as exception_error: + print(f"[DEBUG] Error updating status label: {exception_error}") \ No newline at end of file diff --git a/src/main.py b/src/main.py index 3907a15..60d80bf 100644 --- a/src/main.py +++ b/src/main.py @@ -9,27 +9,7 @@ try: from .gui.main_window import main except ImportError: # Fall back to direct import (when run directly) - try: - from gui.main_window import main - except ImportError: - from gui import AndroidFileHandlerGUI - import sys - - def main(): - """Main function to run the application.""" - if sys.platform not in ["win32", "linux"]: - import tkinter as tk - from tkinter import messagebox - - root = tk.Tk() - root.withdraw() - messagebox.showerror( - "Unsupported OS", "This application only supports Windows or Linux." - ) - sys.exit(1) - - app = AndroidFileHandlerGUI() - app.mainloop() + from gui.main_window import main if __name__ == "__main__":