diff --git a/fastanime/cli/service/feedback/service.py b/fastanime/cli/service/feedback/service.py index f11abb3..8eebfea 100644 --- a/fastanime/cli/service/feedback/service.py +++ b/fastanime/cli/service/feedback/service.py @@ -1,3 +1,4 @@ +import logging from contextlib import contextmanager from typing import Optional @@ -13,6 +14,8 @@ from rich.progress import ( from ....core.config import AppConfig +logger = logging.getLogger(__name__) + console = Console() @@ -20,13 +23,29 @@ class FeedbackService: """Centralized manager for user feedback in interactive menus.""" def __init__(self, config: AppConfig): - self.config = config + self.app_config = config def success(self, message: str, details: Optional[str] = None) -> None: """Show a success message with optional details.""" - icon = "✅ " if self.config.general.icons else "" + icon = "✅ " if self.app_config.general.icons else "" main_msg = f"[bold green]{icon}{message}[/bold green]" + if self.app_config.general.selector == "rofi": + try: + from plyer import notification + + from ....core.constants import ICON_PATH, PROJECT_NAME + + notification.notify( # type: ignore + title=f"{PROJECT_NAME} notification".title(), + message=message, + app_name=PROJECT_NAME, + app_icon=str(ICON_PATH), + timeout=self.app_config.general.desktop_notification_duration * 60, + ) + return + except: + logger.warning("Using rofi without plyer for notifications") if details: console.print(f"{main_msg}\n[dim]{details}[/dim]") else: @@ -34,9 +53,25 @@ class FeedbackService: def error(self, message: str, details: Optional[str] = None) -> None: """Show an error message with optional details.""" - icon = "❌ " if self.config.general.icons else "" + icon = "❌ " if self.app_config.general.icons else "" main_msg = f"[bold red]{icon}Error: {message}[/bold red]" + if self.app_config.general.selector == "rofi": + try: + from plyer import notification + + from ....core.constants import ICON_PATH, PROJECT_NAME + + notification.notify( # type: ignore + title=f"{PROJECT_NAME} notification".title(), + message=message, + app_name=PROJECT_NAME, + app_icon=str(ICON_PATH), + timeout=self.app_config.general.desktop_notification_duration * 60, + ) + return + except: + logger.warning("Using rofi without plyer for notifications") if details: console.print(f"{main_msg}\n[dim]{details}[/dim]") else: @@ -45,9 +80,25 @@ class FeedbackService: def warning(self, message: str, details: Optional[str] = None) -> None: """Show a warning message with optional details.""" - icon = "⚠️ " if self.config.general.icons else "" + icon = "⚠️ " if self.app_config.general.icons else "" main_msg = f"[bold yellow]{icon}Warning: {message}[/bold yellow]" + if self.app_config.general.selector == "rofi": + try: + from plyer import notification + + from ....core.constants import ICON_PATH, PROJECT_NAME + + notification.notify( # type: ignore + title=f"{PROJECT_NAME} notification".title(), + message=message, + app_name=PROJECT_NAME, + app_icon=str(ICON_PATH), + timeout=self.app_config.general.desktop_notification_duration * 60, + ) + return + except: + logger.warning("Using rofi without plyer for notifications") if details: console.print(f"{main_msg}\n[dim]{details}[/dim]") else: @@ -55,9 +106,25 @@ class FeedbackService: def info(self, message: str, details: Optional[str] = None) -> None: """Show an informational message with optional details.""" - icon = "" if self.config.general.icons else "" + icon = "" if self.app_config.general.icons else "" main_msg = f"[bold blue]{icon}{message}[/bold blue]" + if self.app_config.general.selector == "rofi": + try: + from plyer import notification + + from ....core.constants import ICON_PATH, PROJECT_NAME + + notification.notify( # type: ignore + title=f"{PROJECT_NAME} notification".title(), + message=message, + app_name=PROJECT_NAME, + app_icon=str(ICON_PATH), + timeout=self.app_config.general.desktop_notification_duration * 60, + ) + return + except: + logger.warning("Using rofi without plyer for notifications") if details: console.print(f"{main_msg}\n[dim]{details}[/dim]") else: @@ -76,7 +143,7 @@ class FeedbackService: ): """Context manager for operations with loading indicator and result feedback.""" with Progress( - SpinnerColumn(self.config.general.preferred_spinner), + SpinnerColumn(self.app_config.general.preferred_spinner), TextColumn(f"[cyan]{message}..."), BarColumn(), TaskProgressColumn(), @@ -96,7 +163,24 @@ class FeedbackService: def pause_for_user(self, message: str = "Press Enter to continue") -> None: """Pause execution and wait for user input.""" - icon = "⏸️ " if self.config.general.icons else "" + icon = "⏸️ " if self.app_config.general.icons else "" + + if self.app_config.general.selector == "rofi": + try: + from plyer import notification + + from ....core.constants import ICON_PATH, PROJECT_NAME + + notification.notify( # type: ignore + title=f"{PROJECT_NAME} notification".title(), + message="No current way to display info in rofi, use fzf and the terminal instead", + app_name=PROJECT_NAME, + app_icon=str(ICON_PATH), + timeout=self.app_config.general.desktop_notification_duration * 60, + ) + return + except: + logger.warning("Using rofi without plyer for notifications") click.pause(f"{icon}{message}...") def clear_console(self): diff --git a/fastanime/libs/selectors/rofi/selector.py b/fastanime/libs/selectors/rofi/selector.py index abbd886..f913684 100644 --- a/fastanime/libs/selectors/rofi/selector.py +++ b/fastanime/libs/selectors/rofi/selector.py @@ -1,3 +1,4 @@ +import logging import shutil import subprocess import sys @@ -7,6 +8,8 @@ from ....core.config import RofiConfig from ....core.utils import detect from ..base import BaseSelector +logger = logging.getLogger(__name__) + class RofiSelector(BaseSelector): def __init__(self, config: RofiConfig): @@ -47,6 +50,24 @@ class RofiSelector(BaseSelector): return choice else: # HACK: force exit if no input + try: + from plyer import notification + + from ....core.constants import ( + ICON_PATH, + PROJECT_NAME, + PROJECT_NAME_LOWER, + ) + + notification.notify( # type: ignore + title=f"{PROJECT_NAME} notification".title(), + message=f"Nothing was selected {PROJECT_NAME_LOWER} is shutting down", + app_name=PROJECT_NAME, + app_icon=str(ICON_PATH), + timeout=2 * 60, + ) + except: + logger.warning("Using rofi without plyer for notifications") sys.exit(1) def confirm(self, prompt, *, default=False): @@ -66,12 +87,14 @@ class RofiSelector(BaseSelector): def choose_multiple( self, prompt: str, choices: list[str], preview: str | None = None ) -> list[str]: - rofi_input = "\n".join(choices) + if preview and detect.is_bash_script(preview): + preview = None + rofi_input = preview or "\n".join(choices) args = [ self.executable, "-no-config", "-theme", - self.config.theme_main, + self.config.theme_main if not preview else self.config.theme_preview, "-multi-select", "-p", prompt, @@ -86,9 +109,31 @@ class RofiSelector(BaseSelector): ) if result.returncode == 0: - choice = result.stdout.strip() - return choice.split() + selections = [ + line.strip() + for line in result.stdout.strip().split("\n") + if line.strip() + ] + return selections + try: + from plyer import notification + + from ....core.constants import ( + ICON_PATH, + PROJECT_NAME, + PROJECT_NAME_LOWER, + ) + + notification.notify( # type: ignore + title=f"{PROJECT_NAME} notification".title(), + message=f"Nothing was selected {PROJECT_NAME_LOWER} is shutting down", + app_name=PROJECT_NAME, + app_icon=str(ICON_PATH), + timeout=2 * 60, + ) + except: + logger.warning("Using rofi without plyer for notifications") # HACK: force exit if no input sys.exit(1)