1- from textual ._types import IgnoreReturnCallbackType
2- from textual .command import Hits , Provider as TextualProvider , Hit , DiscoveryHit
31import argparse
42import logging
53from asyncio import gather , set_event_loop , new_event_loop
1311from time import sleep , time
1412from typing import ClassVar , List , Union
1513from async_lru import alru_cache
14+ from os import getenv
1615
1716from fuzzywuzzy import fuzz
1817from platformdirs import user_config_path , user_log_path
4140 TabPane ,
4241)
4342from textual .worker import get_current_worker
44-
43+ from rich_argparse import RichHelpFormatter
4544from .aniskip import (
4645 generate_chapters_file ,
4746 get_timings_from_search
5857from .settings import gucken_settings_manager
5958from .update import check
6059from .utils import detect_player , is_android , set_default_vlc_interface_cfg , get_vlc_intf_user_path
61-
60+ from . import __version__
6261
6362def sort_favorite_lang (
6463 language_list : List [Language ], pio_list : List [str ]
@@ -204,59 +203,17 @@ def move_item(lst: list, from_index: int, to_index: int) -> list:
204203CLIENT_ID = "1238219157464416266"
205204
206205
207- class GuckenCommands (TextualProvider ):
208-
209- @property
210- def _commands (self ) -> tuple [tuple [str , IgnoreReturnCallbackType , str ], ...]:
211- return (
212- (
213- "Toggle light/dark mode" , # 🌇 / 🌃
214- self .app .action_toggle_dark ,
215- "Toggle the application between light and dark mode" ,
216- ),
217- (
218- "Quit the application" , # ❌
219- self .app .action_quit ,
220- "Quit the application as soon as possible" ,
221- ),
222- )
223-
224- """
225- (
226- "Application folders", # 📁
227- self.app.action_quit,
228- "Displays a list of all folders used",
229- ),
230- (
231- "Create Shortcut", # 🔗
232- self.app.action_quit,
233- "The will create shortcuts to gucken",
234- )
235- """
236-
237- async def discover (self ) -> Hits :
238- for name , runnable , help_text in self ._commands :
239- yield DiscoveryHit (name , runnable , help = help_text )
240-
241- async def search (self , query : str ) -> Hits :
242- matcher = self .matcher (query )
243- for name , runnable , help_text in self ._commands :
244- if (match := matcher .match (name )) > 0 :
245- yield Hit (match , matcher .highlight (name ), runnable , help = help_text )
246-
247-
248206class GuckenApp (App ):
249- TITLE = "Gucken TUI"
250- # TODO: color theme https://textual.textualize.io/guide/design/#designing-with-colors
251-
207+ TITLE = f"Gucken { __version__ } "
252208 CSS_PATH = [join ("resources" , "gucken.css" )]
253209 custom_css = user_config_path ("gucken" ).joinpath ("custom.css" )
254210 if custom_css .exists ():
255211 CSS_PATH .append (custom_css )
256212 BINDINGS : ClassVar [list [BindingType ]] = [
257213 Binding ("q" , "quit" , "Quit" , show = False , priority = False ),
258214 ]
259- COMMANDS = {GuckenCommands }
215+
216+ # TODO: theme_changed_signal
260217
261218 def __init__ (self , debug : bool , search : str ):
262219 super ().__init__ (watch_css = debug )
@@ -322,7 +279,7 @@ def compose(self) -> ComposeResult:
322279 yield ClickableDataTable (id = "season_list" )
323280 with TabPane ("Settings" , id = "setting" ): # Settings "⚙"
324281 # TODO: dont show unneeded on android
325- with ScrollableContainer ():
282+ with ScrollableContainer (id = "settings_container" ):
326283 yield SortableTable (id = "lang" )
327284 yield SortableTable (id = "host" )
328285 yield RadioButton (
@@ -373,7 +330,7 @@ def compose(self) -> ComposeResult:
373330 )
374331 # yield Footer()
375332 with Center (id = "footer" ):
376- yield Label ("Made by Commandcracker with [red]:heart: [/red]" )
333+ yield Label ("Made by Commandcracker with [red]❤ [/red]" )
377334
378335 @on (Input .Changed )
379336 async def input_changed (self , event : Input .Changed ):
@@ -445,12 +402,12 @@ def select_changed(self, event: Select.Changed) -> None:
445402
446403 # TODO: dont lock - no async
447404 async def on_mount (self ) -> None :
448- self .dark = gucken_settings_manager .settings ["settings" ]["ui" ]["dark " ]
405+ self .theme = getenv ( "TEXTUAL_THEME" ) or gucken_settings_manager .settings ["settings" ]["ui" ]["theme " ]
449406
450- def update_dark ( value : bool ) :
451- gucken_settings_manager .settings ["settings" ]["ui" ]["dark " ] = value
407+ def on_theme_change ( old_value : str , new_value : str ) -> None :
408+ gucken_settings_manager .settings ["settings" ]["ui" ]["theme " ] = new_value
452409
453- self .watch (self , "dark " , update_dark )
410+ self .watch (self . app , "theme " , on_theme_change , init = False )
454411
455412 lang = self .query_one ("#lang" , DataTable )
456413 lang .add_columns ("Language" )
@@ -471,11 +428,10 @@ def set_search():
471428
472429 self .call_later (set_search )
473430
474- self .query_one ("#info" , TabPane ).loading = True
431+ self .query_one ("#info" , TabPane ).set_loading ( True )
475432
476433 table = self .query_one ("#season_list" , DataTable )
477434 table .cursor_type = "row"
478- table .add_columns ("FT" , "S" , "F" , "Title" , "Hoster" , "Sprache" )
479435
480436 if self .query_one ("#update_checker" , RadioButton ).value is True :
481437 self .update_check ()
@@ -534,7 +490,7 @@ def lookup_anime(self, keyword: str) -> None:
534490 if keyword is None :
535491 if not worker .is_cancelled :
536492 self .call_from_thread (results_list_view .clear )
537- results_list_view .loading = False
493+ self . call_from_thread ( results_list_view .set_loading , False )
538494 return
539495
540496 aniworld_to = self .query_one ("#aniworld_to" , Checkbox ).value
@@ -550,7 +506,7 @@ def lookup_anime(self, keyword: str) -> None:
550506 if worker .is_cancelled :
551507 return
552508 self .call_from_thread (results_list_view .clear )
553- results_list_view .loading = True
509+ self . call_from_thread ( results_list_view .set_loading , True )
554510 if worker .is_cancelled :
555511 return
556512 results = self .sync_gather (search_providers )
@@ -577,7 +533,7 @@ def fuzzy_sort_key(result):
577533 if worker .is_cancelled :
578534 return
579535 self .call_from_thread (results_list_view .extend , items )
580- results_list_view .loading = False
536+ self . call_from_thread ( results_list_view .set_loading , False )
581537 if len (final_results ) > 0 :
582538
583539 def select_first_index ():
@@ -621,15 +577,15 @@ async def on_key(self, event: events.Key) -> None:
621577 async def play_selected (self ):
622578 dt = self .query_one ("#season_list" , DataTable )
623579 # TODO: show loading
624- dt .loading = True
580+ # dt.set_loading( True)
625581 index = self .app .query_one ("#results" , ListView ).index
626582 series_search_result = self .current [index ]
627583 self .play (
628584 series_search_result = series_search_result ,
629585 episodes = self .current_info .episodes ,
630586 index = dt .cursor_row ,
631587 )
632- dt .loading = False
588+ # dt.set_loading( False)
633589
634590 @alru_cache (maxsize = 32 , ttl = 600 ) # Cache 32 entries. Clear entry after 10 minutes.
635591 async def get_series (self , series_search_result : SearchResult ):
@@ -642,7 +598,7 @@ async def open_info(self) -> None:
642598 ]
643599 info_tab = self .query_one ("#info" , TabPane )
644600 info_tab .disabled = False
645- info_tab .loading = True
601+ info_tab .set_loading ( True )
646602 table = self .query_one ("#season_list" , DataTable )
647603 table .focus (scroll_visible = False )
648604 md = self .query_one ("#markdown" , Markdown )
@@ -651,7 +607,10 @@ async def open_info(self) -> None:
651607 self .current_info = series
652608 await md .update (series .to_markdown ())
653609
654- table .clear ()
610+ # make sure to reset colum spacing
611+ table .clear (columns = True )
612+ table .add_columns ("FT" , "S" , "F" , "Title" , "Hoster" , "Sprache" )
613+
655614 c = 0
656615 for ep in series .episodes :
657616 hl = []
@@ -671,7 +630,7 @@ async def open_info(self) -> None:
671630 " " .join (sort_favorite_hoster_by_key (hl , self .hoster )),
672631 " " .join (ll ),
673632 )
674- info_tab .loading = False
633+ info_tab .set_loading ( False )
675634
676635 @work (exclusive = True , thread = True )
677636 async def update_check (self ):
@@ -931,19 +890,32 @@ async def play_next(should_next):
931890
932891def main ():
933892 parser = argparse .ArgumentParser (
934- prog = 'Gucken' ,
935- description = "Gucken is a Terminal User Interface which allows you to browse and watch your favorite anime's with style."
893+ prog = 'gucken' ,
894+ description = "Gucken is a Terminal User Interface which allows you to browse and watch your favorite anime's with style." ,
895+ formatter_class = RichHelpFormatter
936896 )
937897 parser .add_argument ("search" , nargs = '?' )
938- parser .add_argument ("--debug" , "--dev" , action = "store_true" )
898+ parser .add_argument (
899+ "--debug" , "--dev" ,
900+ action = "store_true" ,
901+ help = 'enables logging and live tcss reload'
902+ )
903+ parser .add_argument (
904+ '-V' , '--version' ,
905+ action = 'store_true' ,
906+ help = 'display version information.'
907+ )
939908 args = parser .parse_args ()
909+ if args .version :
910+ exit (f"gucken { __version__ } " )
940911 if args .debug :
941912 logs_path = user_log_path ("gucken" , ensure_exists = True )
942913 logging .basicConfig (
943914 filename = logs_path .joinpath ("gucken.log" ), encoding = "utf-8" , level = logging .INFO , force = True
944915 )
945916
946917 register_atexit (gucken_settings_manager .save )
918+ print (f"\033 ]0;Gucken { __version__ } \007 " , end = '' , flush = True )
947919 gucken_app = GuckenApp (debug = args .debug , search = args .search )
948920 gucken_app .run ()
949921 print (choice (exit_quotes ))
0 commit comments