Skip to content

Commit

Permalink
added priority to help/quit keyboard shortcuts; cleaned up README
Browse files Browse the repository at this point in the history
  • Loading branch information
mwhickson committed Nov 23, 2024
1 parent a5be408 commit fee091d
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 33 deletions.
53 changes: 28 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,23 @@ This implementation utilizes Python and Textual -- but no ChatGPT.

## Features

* discover podcasts through iTunes-based search
* play podcast episodes directly from source
* pause podcasts during play
* *more (still in development)*
- discover podcasts through iTunes-based search
- play podcast episodes directly from source
- pause podcasts during play
- *more (still in development)*

## Installation and Running

You must:

* have Python installed
* have `pip` installed (if not included with Python)
* Use:
* a virtual environment
* or, install Python modules from your environment (e.g. apt repository)
* or, use `--break-system-packages`
- have Python installed
- have `pip` installed (if not included with Python)
- Use:
- a virtual environment
- or, install Python modules from your environment (e.g. apt repository)
- or, use `--break-system-packages`

*If you are using Windows, you may need the **Visual Studio Build Tools** to build `miniaudio` during the `pip` install.*

```bash
$ git clone https://github.com/mwhickson/tuipod.git
Expand All @@ -38,24 +40,25 @@ Also provided are batch (`tuipod.bat`) and shell (`tuipod.sh`) files to simplify

## Usage Guide

* type search criteria into the search box and press `ENTER` to fetch and display podcast results
* if no errors occur and podcast results are found, focus will automatically shift to podcast list
* select a podcast item of interest and press `ENTER` to fetch and display a list of episodes for the selected podcast
* if no errors occur and episode results are found, focus will automatically shift to the episode list
* select an episode item of interest and press `ENTER` to begin playing the episode
* if no errors occur, the episode will being playing (the `play` button text will change to `pause` and the button will become green)
- type search criteria into the search box and press `ENTER` to fetch and display podcast results
- if no errors occur and podcast results are found, focus will automatically shift to podcast list
- select a podcast item of interest and press `ENTER` to fetch and display a list of episodes for the selected podcast
- if no errors occur and episode results are found, focus will automatically shift to the episode list
- select an episode item of interest and press `ENTER` to begin playing the episode
- if no errors occur, the episode will being playing (the `play` button text will change to `pause` and the button will become green)

### Additional keyboard shortcuts:

* `TAB` and `SHIFT`+`TAB` will move the cursor focus between sections (e.g. search, podcast list, and episode list)
* while not focused on the search input:
* `q` will quit the application
* `CTRL`+`p` will show the textual command palette
* when an episode is selected:
* `SPACE` will toggle between playing and paused
* `i` will show the episode information
* when a modal screen (episode information/error information) is displayed:
* `ESC` will close the window
- `ESC` will close a modal window
- `F1` will show the About dialog
- `TAB` and `SHIFT`+`TAB` will move the cursor focus between sections (e.g. search, podcast list, and episode list)
- `CTRL`+`Q` will quit the application
- `CTRL`+`P` will show the textual command palette
- when an episode is selected:
- `I` will show the episode information
- `SPACE` will toggle between episode playing/paused state

*NOTE: Some keystrokes depend on application state (e.g. not actively searching, episode playing, etc.)*

## Screenshots

Expand Down
103 changes: 103 additions & 0 deletions tuipod/ui/about_info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
from textual.app import ComposeResult
from textual.containers import Container
from textual.screen import ModalScreen
from textual.widgets import Button, MarkdownViewer, Static


class AboutInfoScreen(ModalScreen):
BINDINGS = [
("escape", "close_modal", "Close modal")
]

DEFAULT_CSS = """
AboutInfoScreen {
align: center middle;
height: auto;
width: auto;
}
#modalContainer {
height: 60%;
width: 80%;
}
#modalTitle {
background: $secondary;
color: $background;
dock: top;
text-align: center;
}
#contentContainer {
padding: 1;
}
#aboutDetail {
padding: 1 0;
height: 1fr;
}
#buttonContainer {
dock: bottom;
height: 3;
padding: 1 2;
}
#closeInfoButton {
background: $secondary;
border: none;
color: $background;
}
"""

# indentation modified to avoid leading spaces, which would cause markdown to be processed as preformatted text (i.e. shown as raw markdown)
# NOPE: also, for simplicity, omitting the conditional, non-CTRL options out (condition users to do it the way that will work everywhere)
# CTRL+SPACE doesn't work; CTRL+I behaves like TAB; CTRL+Q seems okay...
ABOUT_INFO = """\
## tuipod
A simple podcast player with a text-based user interface.
Available at [github.com/mwhickson/tuipod](https://github.com/mwhickson/tuipod)
## Keystrokes
- `ENTER` - submit a search query, or select a list item
- `ESC` - close a modal screen
- `F1` - show this dialog
- `CTRL` + `P` - show the textual command palette
- `CTRL` + `Q` - quit the application
- `D` - toggle dark mode
- `I` - show episode information
- `SPACE` - play/pause an episode after selection
- `TAB` / `SHIFT` + `TAB` - move cursor from section to section
NOTE: Some keystrokes depend on application state (e.g. not actively searching, episode playing, etc.)
"""

# TODO: - `S` - subscribe to the current podcast

def __init__(self) -> None:
super().__init__()
self.detail = self.ABOUT_INFO

def compose(self) -> ComposeResult:
yield Container(
Static("About", id="modalTitle"),
Container(
MarkdownViewer(id="aboutDetail", markdown=self.detail, show_table_of_contents=False),
id="contentContainer"
),
Container(
Button("close", id="closeInfoButton"),
id="buttonContainer"
),
id="modalContainer"
)

def on_button_pressed(self, event: Button.Pressed) -> None:
if event.button.id == "closeInfoButton":
self.app.pop_screen()

def action_close_modal(self) -> None:
self.app.pop_screen()
4 changes: 2 additions & 2 deletions tuipod/ui/episode_info.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from textual.app import ComposeResult
from textual.containers import Container
from textual.screen import ModalScreen
from textual.widgets import Button, Link, Markdown, Static
from textual.widgets import Button, Link, MarkdownViewer, Static


class EpisodeInfoScreen(ModalScreen):
Expand Down Expand Up @@ -72,7 +72,7 @@ def compose(self) -> ComposeResult:
Container(
Static(self.title, id="episodeTitle"),
Link(self.url, id="episodeLink"),
Markdown(self.detail, id="episodeDetail"),
MarkdownViewer(id="episodeDetail", markdown=self.detail, show_table_of_contents=False),
id="contentContainer"
),
Container(
Expand Down
4 changes: 2 additions & 2 deletions tuipod/ui/error_info.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from textual.app import ComposeResult
from textual.containers import Container
from textual.screen import ModalScreen
from textual.widgets import Button, Markdown, Static
from textual.widgets import Button, MarkdownViewer, Static


class ErrorInfoScreen(ModalScreen):
Expand Down Expand Up @@ -58,7 +58,7 @@ def compose(self) -> ComposeResult:
yield Container(
Static("Error Information", id="modalTitle"),
Container(
Markdown(self.detail, id="errorDetail"),
MarkdownViewer(id="errorDetail", markdown=self.detail, show_table_of_contents=False),
id="contentContainer"
),
Container(
Expand Down
23 changes: 19 additions & 4 deletions tuipod/ui/podcast_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

from textual import on
from textual.app import App, ComposeResult
from textual.binding import Binding
from textual.widgets import Button, DataTable, Header, Input, Static

from tuipod.models.search import Search
from tuipod.ui.about_info import AboutInfoScreen
from tuipod.ui.episode_info import EpisodeInfoScreen
from tuipod.ui.episode_list import EpisodeList
from tuipod.ui.error_info import ErrorInfoScreen
Expand All @@ -17,10 +19,17 @@

class PodcastApp(App):
BINDINGS = [
("space", "toggle_play", "Play/Pause"),
("d", "toggle_dark", "Toggle dark mode"),
("i", "display_info", "Display information"),
("q", "quit_application", "Quit application")
Binding("f1", "display_about", "Display about information", priority=True),

# don't let search swallow input (but don't prioritize standard text entry characters to hamper search)
Binding("ctrl+q", "quit_application", "Quit application", priority=True),

# dupes, but lets us avoid the need to CTRL chord keys for the most part (these will be 'undocumented' to avoid confusion re: the conditions necessary for these to work)
# TODO: Binding("space", "toggle_play", "Play/Pause"),
Binding("d", "toggle_dark", "Toggle dark mode"),
Binding("i", "display_info", "Display information"),
Binding("q", "quit_application", "Quit application"),
Binding("s", "subscribe_to_podcast", "Subscribe to Podcast")
]
TITLE = APPLICATION_NAME
SUB_TITLE = "version {0}".format(APPLICATION_VERSION)
Expand Down Expand Up @@ -140,9 +149,15 @@ def action_toggle_play(self) -> None:
play_button.styles.background = "green"
play_button.styles.color = "white"

def action_display_about(self) -> None:
self.app.push_screen(AboutInfoScreen())

def action_display_info(self) -> None:
if not self.current_episode is None:
self.app.push_screen(EpisodeInfoScreen(self.current_episode.title, self.current_episode.url, self.current_episode.description))

def action_quit_application(self) -> None:
self.exit()

# def action_subscribe_to_podcast(self) -> None:
# pass

0 comments on commit fee091d

Please sign in to comment.