1
- from textual ._types import IgnoreReturnCallbackType
2
- from textual .command import Hits , Provider as TextualProvider , Hit , DiscoveryHit
3
1
import argparse
4
2
import logging
5
3
from asyncio import gather , set_event_loop , new_event_loop
13
11
from time import sleep , time
14
12
from typing import ClassVar , List , Union
15
13
from async_lru import alru_cache
14
+ from os import getenv
16
15
17
16
from fuzzywuzzy import fuzz
18
17
from platformdirs import user_config_path , user_log_path
41
40
TabPane ,
42
41
)
43
42
from textual .worker import get_current_worker
44
-
43
+ from rich_argparse import RichHelpFormatter
45
44
from .aniskip import (
46
45
generate_chapters_file ,
47
46
get_timings_from_search
58
57
from .settings import gucken_settings_manager
59
58
from .update import check
60
59
from .utils import detect_player , is_android , set_default_vlc_interface_cfg , get_vlc_intf_user_path
61
-
60
+ from . import __version__
62
61
63
62
def sort_favorite_lang (
64
63
language_list : List [Language ], pio_list : List [str ]
@@ -204,59 +203,17 @@ def move_item(lst: list, from_index: int, to_index: int) -> list:
204
203
CLIENT_ID = "1238219157464416266"
205
204
206
205
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
-
248
206
class 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__ } "
252
208
CSS_PATH = [join ("resources" , "gucken.css" )]
253
209
custom_css = user_config_path ("gucken" ).joinpath ("custom.css" )
254
210
if custom_css .exists ():
255
211
CSS_PATH .append (custom_css )
256
212
BINDINGS : ClassVar [list [BindingType ]] = [
257
213
Binding ("q" , "quit" , "Quit" , show = False , priority = False ),
258
214
]
259
- COMMANDS = {GuckenCommands }
215
+
216
+ # TODO: theme_changed_signal
260
217
261
218
def __init__ (self , debug : bool , search : str ):
262
219
super ().__init__ (watch_css = debug )
@@ -322,7 +279,7 @@ def compose(self) -> ComposeResult:
322
279
yield ClickableDataTable (id = "season_list" )
323
280
with TabPane ("Settings" , id = "setting" ): # Settings "⚙"
324
281
# TODO: dont show unneeded on android
325
- with ScrollableContainer ():
282
+ with ScrollableContainer (id = "settings_container" ):
326
283
yield SortableTable (id = "lang" )
327
284
yield SortableTable (id = "host" )
328
285
yield RadioButton (
@@ -373,7 +330,7 @@ def compose(self) -> ComposeResult:
373
330
)
374
331
# yield Footer()
375
332
with Center (id = "footer" ):
376
- yield Label ("Made by Commandcracker with [red]:heart: [/red]" )
333
+ yield Label ("Made by Commandcracker with [red]❤ [/red]" )
377
334
378
335
@on (Input .Changed )
379
336
async def input_changed (self , event : Input .Changed ):
@@ -445,12 +402,12 @@ def select_changed(self, event: Select.Changed) -> None:
445
402
446
403
# TODO: dont lock - no async
447
404
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 " ]
449
406
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
452
409
453
- self .watch (self , "dark " , update_dark )
410
+ self .watch (self . app , "theme " , on_theme_change , init = False )
454
411
455
412
lang = self .query_one ("#lang" , DataTable )
456
413
lang .add_columns ("Language" )
@@ -471,11 +428,10 @@ def set_search():
471
428
472
429
self .call_later (set_search )
473
430
474
- self .query_one ("#info" , TabPane ).loading = True
431
+ self .query_one ("#info" , TabPane ).set_loading ( True )
475
432
476
433
table = self .query_one ("#season_list" , DataTable )
477
434
table .cursor_type = "row"
478
- table .add_columns ("FT" , "S" , "F" , "Title" , "Hoster" , "Sprache" )
479
435
480
436
if self .query_one ("#update_checker" , RadioButton ).value is True :
481
437
self .update_check ()
@@ -534,7 +490,7 @@ def lookup_anime(self, keyword: str) -> None:
534
490
if keyword is None :
535
491
if not worker .is_cancelled :
536
492
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 )
538
494
return
539
495
540
496
aniworld_to = self .query_one ("#aniworld_to" , Checkbox ).value
@@ -550,7 +506,7 @@ def lookup_anime(self, keyword: str) -> None:
550
506
if worker .is_cancelled :
551
507
return
552
508
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 )
554
510
if worker .is_cancelled :
555
511
return
556
512
results = self .sync_gather (search_providers )
@@ -577,7 +533,7 @@ def fuzzy_sort_key(result):
577
533
if worker .is_cancelled :
578
534
return
579
535
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 )
581
537
if len (final_results ) > 0 :
582
538
583
539
def select_first_index ():
@@ -621,15 +577,15 @@ async def on_key(self, event: events.Key) -> None:
621
577
async def play_selected (self ):
622
578
dt = self .query_one ("#season_list" , DataTable )
623
579
# TODO: show loading
624
- dt .loading = True
580
+ # dt.set_loading( True)
625
581
index = self .app .query_one ("#results" , ListView ).index
626
582
series_search_result = self .current [index ]
627
583
self .play (
628
584
series_search_result = series_search_result ,
629
585
episodes = self .current_info .episodes ,
630
586
index = dt .cursor_row ,
631
587
)
632
- dt .loading = False
588
+ # dt.set_loading( False)
633
589
634
590
@alru_cache (maxsize = 32 , ttl = 600 ) # Cache 32 entries. Clear entry after 10 minutes.
635
591
async def get_series (self , series_search_result : SearchResult ):
@@ -642,7 +598,7 @@ async def open_info(self) -> None:
642
598
]
643
599
info_tab = self .query_one ("#info" , TabPane )
644
600
info_tab .disabled = False
645
- info_tab .loading = True
601
+ info_tab .set_loading ( True )
646
602
table = self .query_one ("#season_list" , DataTable )
647
603
table .focus (scroll_visible = False )
648
604
md = self .query_one ("#markdown" , Markdown )
@@ -651,7 +607,10 @@ async def open_info(self) -> None:
651
607
self .current_info = series
652
608
await md .update (series .to_markdown ())
653
609
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
+
655
614
c = 0
656
615
for ep in series .episodes :
657
616
hl = []
@@ -671,7 +630,7 @@ async def open_info(self) -> None:
671
630
" " .join (sort_favorite_hoster_by_key (hl , self .hoster )),
672
631
" " .join (ll ),
673
632
)
674
- info_tab .loading = False
633
+ info_tab .set_loading ( False )
675
634
676
635
@work (exclusive = True , thread = True )
677
636
async def update_check (self ):
@@ -931,19 +890,32 @@ async def play_next(should_next):
931
890
932
891
def main ():
933
892
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
936
896
)
937
897
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
+ )
939
908
args = parser .parse_args ()
909
+ if args .version :
910
+ exit (f"gucken { __version__ } " )
940
911
if args .debug :
941
912
logs_path = user_log_path ("gucken" , ensure_exists = True )
942
913
logging .basicConfig (
943
914
filename = logs_path .joinpath ("gucken.log" ), encoding = "utf-8" , level = logging .INFO , force = True
944
915
)
945
916
946
917
register_atexit (gucken_settings_manager .save )
918
+ print (f"\033 ]0;Gucken { __version__ } \007 " , end = '' , flush = True )
947
919
gucken_app = GuckenApp (debug = args .debug , search = args .search )
948
920
gucken_app .run ()
949
921
print (choice (exit_quotes ))
0 commit comments