feat(ui): add catalogue picker, schedule pane and per-provider hero
This commit is contained in:
1 parent
058d336a2c
commit
b31ad3f6fb
16 files changed
+1211
-65
No files matched your search
@@ -0,0 +1,87 @@
|
||||
"""Catalogue picker: switch between SomaFM, tilderadio and whatever comes next."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from rich.console import Group
|
||||
from rich.text import Text
|
||||
from textual.app import ComposeResult
|
||||
from textual.screen import ModalScreen
|
||||
from textual.widgets import Static
|
||||
|
||||
from ..providers import PROVIDERS, available_providers
|
||||
from .common import SPECTRUM_STOPS, gradient, truncate
|
||||
|
||||
|
||||
class ProviderScreen(ModalScreen[str | None]):
|
||||
"""A list of catalogues; enter selects, esc leaves everything as it was."""
|
||||
|
||||
def __init__(self, current_id: str = "") -> None:
|
||||
super().__init__()
|
||||
self.slugs: tuple[str, ...] = available_providers()
|
||||
self.current_id = current_id
|
||||
self.index = self.slugs.index(current_id) if current_id in self.slugs else 0
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
yield Static(self._body(), id="providers")
|
||||
|
||||
def _body(self) -> Group:
|
||||
header = Text()
|
||||
header.append("◤ ", style=f"bold {gradient(SPECTRUM_STOPS, 0.9)}")
|
||||
header.append("CATALOGUES", style=f"bold {gradient(SPECTRUM_STOPS, 0.55)}")
|
||||
header.append(" pick a station catalogue", style="dim")
|
||||
|
||||
rows: list[Text] = []
|
||||
for index, slug in enumerate(self.slugs):
|
||||
provider = PROVIDERS[slug]
|
||||
selected = index == self.index
|
||||
row = Text(no_wrap=True)
|
||||
row.append("▶ " if selected else " ", style="bold magenta" if selected else "")
|
||||
row.append(f"{index + 1} ", style="dim")
|
||||
name_style = "bold underline" if selected else "bold"
|
||||
row.append(f"{provider.name:<12}", style=name_style)
|
||||
row.append(f" {provider.brand}", style="dim")
|
||||
if slug == self.current_id:
|
||||
row.append(" ●", style="bold green")
|
||||
rows.append(row)
|
||||
|
||||
detail = Text(no_wrap=True)
|
||||
detail.append(" ", style="")
|
||||
detail.append(truncate(provider.tagline, 54), style="italic dim")
|
||||
rows.append(detail)
|
||||
|
||||
current = PROVIDERS.get(self.current_id)
|
||||
footer = Text()
|
||||
footer.append("\n")
|
||||
if current is not None:
|
||||
footer.append(
|
||||
f"now: {current.name} — {current.station_source}\n", style="dim"
|
||||
)
|
||||
footer.append(
|
||||
"↑ ↓ choose · 1–9 jump · enter switch · esc cancel",
|
||||
style="dim",
|
||||
)
|
||||
return Group(header, Text(""), *rows, footer)
|
||||
|
||||
# -- interaction ----------------------------------------------------- #
|
||||
|
||||
def _move(self, delta: int) -> None:
|
||||
self.index = (self.index + delta) % len(self.slugs)
|
||||
self.query_one("#providers", Static).update(self._body())
|
||||
|
||||
def on_key(self, event) -> None:
|
||||
key = event.key
|
||||
if key in ("up", "k", "shift+tab"):
|
||||
self._move(-1)
|
||||
elif key in ("down", "j", "tab"):
|
||||
self._move(1)
|
||||
elif key in ("enter", "space"):
|
||||
self.dismiss(self.slugs[self.index])
|
||||
elif key == "escape" or key == "q":
|
||||
self.dismiss(None)
|
||||
elif key.isdigit() and key != "0":
|
||||
choice = int(key) - 1
|
||||
if 0 <= choice < len(self.slugs):
|
||||
self.dismiss(self.slugs[choice])
|
||||
else:
|
||||
return
|
||||
event.stop()
|
||||
Reference in new issue
Block a user