Krolyxon
Commit 04e5d2a

rmptui04e5d2a

alot of edge cases removed, ui improvments

Author
krolxon
Date
2024-04-26 14:32 +0530
Parent
59e8e8c
Commit
04e5d2ad28af2c0b561ed4443eabddc4ee70d1f2

+319 −111

13 files changed

  1. M Cargo.lock +7 −0
  2. M Cargo.toml +1 −0
  3. M README.md +12 −3
  4. M src/app.rs +2 −19
  5. M src/browser.rs +29 −14
  6. M src/cli.rs +2 −2
  7. M src/connection.rs +47 −17
  8. M src/handler.rs +39 −5
  9. M src/lib.rs +3 −0
  10. M src/list.rs +12 −6
  11. M src/main.rs +35 −23
  12. A src/song.rs +67 −0
  13. M src/ui.rs +63 −22
M Cargo.lock View
@@ -225,6 +225,12 @@ version = "0.5.0"
225225 source = "registry+https://github.com/rust-lang/crates.io-index"
226226 checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
227227
228+[[package]]
229+name = "humantime"
230+version = "2.1.0"
231+source = "registry+https://github.com/rust-lang/crates.io-index"
232+checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
233+
228234 [[package]]
229235 name = "indoc"
230236 version = "2.0.5"
@@ -386,6 +392,7 @@ version = "0.1.0"
386392 dependencies = [
387393 "clap",
388394 "crossterm",
395+ "humantime",
389396 "mpd",
390397 "ratatui",
391398 "rust-fuzzy-search",
M Cargo.toml View
@@ -13,3 +13,4 @@ simple-dmenu = "0.1.0"
1313 ratatui = "0.26.2"
1414 crossterm = "0.27.0"
1515 rust-fuzzy-search = "0.1.1"
16+humantime = "2.1.0"
M README.md View
@@ -1,7 +1,7 @@
11 A MPD client in Rust
22
33 ### Keys
4−- `q` OR 'Ctr+C' to quit
4+- `q` OR `Ctr+C` to quit
55 - `p` to toggle pause
66 - `+` to increase volume
77 - `-` to decrease volume
@@ -16,9 +16,18 @@ A MPD client in Rust
1616 - `3` to go to playlists view
1717 - `Enter` to add song/playlist to current playlist
1818 - `a` to append the song to current playing queue
19−- `f` to go forward
20−- `b` to go backwords
19+- `f` to go forwards
20+- `b` to go backwards
2121 - `>` to play next song from queue
2222 - `<` to play previous song from queue
2323 - `U` to update the MPD database
24+- `r` to toggle repeat
25+- `z` to toggle random
2426
27+### TODO
28+- [x] fix performance issues
29+- [ ] improvements on queue control
30+- [ ] add to playlists, playlists view
31+- [ ] search for songs
32+- [ ] metadata based tree view
33+- [ ] Humantime format
M src/app.rs View
@@ -14,7 +14,6 @@ pub struct App {
1414 /// check if app is running
1515 pub running: bool,
1616 pub conn: Connection,
17− pub song_list: ContentList<String>,
1817 pub queue_list: ContentList<String>,
1918 pub pl_list: ContentList<String>,
2019 pub selected_tab: SelectedTab,
@@ -28,16 +27,6 @@ pub enum SelectedTab {
2827 Playlists,
2928 }
3029
31−impl SelectedTab {
32− fn as_usize(&self) {
33− match self {
34− SelectedTab::Queue => 0,
35− SelectedTab::Playlists => 1,
36− SelectedTab::DirectoryBrowser => 2,
37− };
38− }
39−}
40−
4130 impl App {
4231 pub fn builder(addrs: &str) -> AppResult<Self> {
4332 let mut conn = Connection::new(addrs).unwrap();
@@ -47,14 +36,10 @@ impl App {
4736
4837 Self::get_queue(&mut conn, &mut queue_list.list);
4938
50− let mut song_list = ContentList::new();
51− song_list.list = conn.songs_filenames.clone();
52−
5339 let browser = FileBrowser::new();
5440 Ok(Self {
5541 running: true,
5642 conn,
57− song_list,
5843 queue_list,
5944 pl_list,
6045 selected_tab: SelectedTab::DirectoryBrowser,
@@ -63,11 +48,9 @@ impl App {
6348 }
6449
6550 pub fn tick(&mut self) {
66− self.conn.update_state();
67− self.conn.update_progress();
68− self.conn.update_volume();
51+ self.conn.update_status();
6952 self.update_queue();
70− self.browser.update_directory(&mut self.conn).unwrap();
53+ // self.browser.update_directory(&mut self.conn).unwrap();
7154 }
7255
7356 pub fn quit(&mut self) {
M src/browser.rs View
@@ -1,3 +1,5 @@
1+use mpd::Query;
2+
13 use crate::{app::AppResult, connection::Connection};
24
35 #[derive(Debug)]
@@ -59,32 +61,45 @@ impl FileBrowser {
5961 if t == "directory" {
6062 if path != "." {
6163 self.prev_path = self.path.clone();
62− self.path = path.to_string();
64+ self.path = self.prev_path.clone() + "/" + path;
6365 self.update_directory(conn)?;
6466 self.prev_selected = self.selected;
6567 self.selected = 0;
68+ // println!("self.path: {}", self.path);
69+ // println!("self.prev_pat: {}", self.prev_path);
6670 }
6771 } else {
68− let list = conn
69− .songs_filenames
70− .iter()
71− .map(|f| f.as_str())
72− .collect::<Vec<&str>>();
73− let (filename, _) = rust_fuzzy_search::fuzzy_search_sorted(&path, &list)
74− .get(0)
75− .unwrap()
76− .clone();
72+ // let list = conn
73+ // .songs_filenames
74+ // .iter()
75+ // .map(|f| f.as_str())
76+ // .collect::<Vec<&str>>();
77+ // let (filename, _) = rust_fuzzy_search::fuzzy_search_sorted(&path, &list)
78+ // .get(0)
79+ // .unwrap()
80+ // .clone();
7781
78− let song = conn.get_song_with_only_filename(filename);
79− conn.push(&song)?;
82+ for filename in conn.songs_filenames.clone().iter() {
83+ if filename.contains(path) {
84+ let song = conn.get_song_with_only_filename(filename);
85+ conn.push(&song)?;
86+ }
87+ }
8088 }
8189 Ok(())
8290 }
8391
8492 pub fn handle_go_back(&mut self, conn: &mut Connection) -> AppResult<()> {
85− self.path = self.prev_path.clone();
93+ if self.prev_path != "." {
94+ let r = self.path.rfind("/").unwrap();
95+ self.path = self.path.as_str()[..r].to_string();
96+ self.update_directory(conn)?;
97+ } else {
98+ self.path = self.prev_path.clone();
99+ self.update_directory(conn)?;
100+ }
101+
86102 self.selected = self.prev_selected;
87− self.update_directory(conn)?;
88103 Ok(())
89104 }
90105 }
M src/cli.rs View
@@ -6,10 +6,10 @@ use clap::{Parser, Subcommand};
66 pub struct Args {
77 /// No TUI
88 #[arg(short= 'n', default_value="false")]
9− pub no_tui: bool,
9+ pub tui: bool,
1010
1111 #[command(subcommand)]
12− pub command: Command,
12+ pub command: Option<Command>,
1313 }
1414
1515 #[derive(Debug, Subcommand)]
M src/connection.rs View
@@ -15,6 +15,8 @@ pub struct Connection {
1515 pub elapsed: Duration,
1616 pub total_duration: Duration,
1717 pub volume: u8,
18+ pub repeat: bool,
19+ pub random: bool,
1820 }
1921
2022 impl Connection {
@@ -27,8 +29,11 @@ impl Connection {
2729 .into_iter()
2830 .map(|x| x.file)
2931 .collect();
30− let (elapsed, total) = conn.status().unwrap().time.unwrap_or_default();
31− let volume: u8 = conn.status().unwrap_or_default().volume as u8;
32+ let status = conn.status().unwrap();
33+ let (elapsed, total) = status.time.unwrap_or_default();
34+ let volume: u8 = status.volume as u8;
35+ let repeat = status.repeat;
36+ let random = status.random;
3237
3338 Ok(Self {
3439 conn,
@@ -37,6 +42,8 @@ impl Connection {
3742 elapsed,
3843 total_duration: total,
3944 volume,
45+ repeat,
46+ random,
4047 })
4148 }
4249
@@ -64,25 +71,33 @@ impl Connection {
6471 Ok(())
6572 }
6673
67− pub fn update_state(&mut self) -> String {
68− match self.conn.status().unwrap().state {
74+ /// Update status
75+ pub fn update_status(&mut self) {
76+ let status = self.conn.status().unwrap();
77+
78+ // Playback State
79+ match status.state {
6980 State::Stop => self.state = "Stopped".to_string(),
7081 State::Play => self.state = "Playing".to_string(),
7182 State::Pause => self.state = "Paused".to_string(),
7283 }
73− self.state.clone()
74− }
7584
76− pub fn update_progress(&mut self) {
77− let (elapsed, total) = self.conn.status().unwrap().time.unwrap_or_default();
85+ // Progress
86+ let (elapsed, total) = status.time.unwrap_or_default();
7887 self.elapsed = elapsed;
7988 self.total_duration = total;
80− }
8189
82− pub fn update_volume(&mut self) {
83− self.volume = self.conn.status().unwrap_or_default().volume as u8;
90+ // Volume
91+ self.volume = status.volume as u8;
92+
93+ // Repeat mode
94+ self.repeat = status.repeat;
95+
96+ // Random mode
97+ self.random = status.random;
8498 }
8599
100+ /// Get progress ratio of current playing song
86101 pub fn get_progress_ratio(&self) -> f64 {
87102 let total = self.total_duration.as_secs_f64();
88103 if total == 0.0 {
@@ -143,11 +158,6 @@ impl Connection {
143158 }
144159 }
145160
146− /// get current playing song
147− pub fn get_current_song(&mut self) -> Option<String> {
148− self.conn.currentsong().unwrap().unwrap_or_default().title
149− }
150−
151161 /// Print status to stdout
152162 pub fn status(&mut self) {
153163 let current_song = self.conn.currentsong();
@@ -170,7 +180,7 @@ impl Connection {
170180 let song = self.conn.currentsong()?.unwrap_or_default();
171181 if let Some(s) = song.title {
172182 if let Some(a) = song.artist {
173− return Ok(Some(format!("\"{}\" By {}", a, s)));
183+ return Ok(Some(format!("\"{}\" By {}", s, a)));
174184 } else {
175185 return Ok(Some(s));
176186 }
@@ -190,7 +200,26 @@ impl Connection {
190200 self.conn.toggle_pause().unwrap();
191201 }
192202
203+ /// Toggle Repeat mode
204+ pub fn toggle_repeat(&mut self) {
205+ if self.conn.status().unwrap().repeat {
206+ self.conn.repeat(false).unwrap();
207+ } else {
208+ self.conn.repeat(true).unwrap();
209+ }
210+ }
211+
212+ /// Toggle random mode
213+ pub fn toggle_random(&mut self) {
214+ if self.conn.status().unwrap().random {
215+ self.conn.random(false).unwrap();
216+ } else {
217+ self.conn.random(true).unwrap();
218+ }
219+ }
220+
193221 // Volume controls
222+ /// Increase Volume
194223 pub fn inc_volume(&mut self, v: i8) {
195224 let cur = self.conn.status().unwrap().volume;
196225 if cur + v <= 100 {
@@ -198,6 +227,7 @@ impl Connection {
198227 }
199228 }
200229
230+ /// Decrease volume
201231 pub fn dec_volume(&mut self, v: i8) {
202232 let cur = self.conn.status().unwrap().volume;
203233 if cur - v >= 0 {
M src/handler.rs View
@@ -13,6 +13,7 @@ pub fn handle_key_events(key_event: KeyEvent, app: &mut App) -> AppResult<()> {
1313 app.quit();
1414 } else {
1515 app.conn.conn.clear()?;
16+ app.conn.update_status();
1617 }
1718 }
1819
@@ -47,6 +48,7 @@ pub fn handle_key_events(key_event: KeyEvent, app: &mut App) -> AppResult<()> {
4748 .push_playlist(app.pl_list.list.get(app.pl_list.index).unwrap())?;
4849 }
4950 }
51+ app.conn.update_status();
5052 }
5153
5254 KeyCode::Char('h') => match app.selected_tab {
@@ -68,6 +70,18 @@ pub fn handle_key_events(key_event: KeyEvent, app: &mut App) -> AppResult<()> {
6870 app.conn.pause();
6971 }
7072
73+ // Toggle rpeat
74+ KeyCode::Char('r') => {
75+ app.conn.toggle_repeat();
76+ app.conn.update_status();
77+ }
78+
79+ // Toggle random
80+ KeyCode::Char('z') => {
81+ app.conn.toggle_random();
82+ app.conn.update_status();
83+ }
84+
7185 // Dmenu prompt
7286 KeyCode::Char('D') => {
7387 app.conn.play_dmenu()?;
@@ -75,9 +89,23 @@ pub fn handle_key_events(key_event: KeyEvent, app: &mut App) -> AppResult<()> {
7589
7690 // add to queue
7791 KeyCode::Char('a') => {
78− let song = app.conn.get_song_with_only_filename(
79− app.conn.songs_filenames.get(app.song_list.index).unwrap(),
80− );
92+ // let song = app.conn.get_song_with_only_filename(
93+ // app.conn.songs_filenames.get(app.song_list.index).unwrap(),
94+ // );
95+
96+ let list = app
97+ .conn
98+ .songs_filenames
99+ .iter()
100+ .map(|f| f.as_str())
101+ .collect::<Vec<&str>>();
102+ let (filename, _) = rust_fuzzy_search::fuzzy_search_sorted(&app.browser.path, &list)
103+ .get(0)
104+ .unwrap()
105+ .clone();
106+
107+ let song = app.conn.get_song_with_only_filename(filename);
108+
81109 app.conn.conn.push(&song)?;
82110 }
83111
@@ -127,16 +155,22 @@ pub fn handle_key_events(key_event: KeyEvent, app: &mut App) -> AppResult<()> {
127155 // Volume controls
128156 KeyCode::Char('=') => {
129157 app.conn.inc_volume(2);
130− app.conn.update_volume();
158+ app.conn.update_status();
131159 }
132160
133161 KeyCode::Char('-') => {
134162 app.conn.dec_volume(2);
135− app.conn.update_volume();
163+ app.conn.update_status();
136164 }
137165
138166 // Delete highlighted song from the queue
139167 KeyCode::Char('d') => {
168+ if app.queue_list.index >= app.queue_list.list.len() - 1 {
169+ if app.queue_list.index != 0 {
170+ app.queue_list.index -= 1;
171+ }
172+ }
173+
140174 app.conn.conn.delete(app.queue_list.index as u32)?;
141175 app.update_queue();
142176 }
M src/lib.rs View
@@ -13,6 +13,9 @@ pub mod tui;
1313 /// Content list
1414 pub mod list;
1515
16+/// Song
17+pub mod song;
18+
1619 /// File Browser
1720 pub mod browser;
1821
M src/list.rs View
@@ -18,18 +18,24 @@ impl<T> ContentList<T> {
1818 // self.index += 1;
1919 // }
2020
21− if self.index == self.list.len() - 1 {
22− self.index = 0;
23− } else {
24− self.index += 1;
21+ let len = self.list.len();
22+ if len != 0 {
23+ if self.index == self.list.len() - 1 {
24+ self.index = 0;
25+ } else {
26+ self.index += 1;
27+ }
2528 }
2629 }
2730
2831 /// Go to previous item in list
2932 pub fn prev(&mut self) {
3033 if self.index == 0 {
31− self.index = self.list.len() - 1;
32− } else {
34+ let len = self.list.len();
35+ if len != 0 {
36+ self.index = len - 1;
37+ }
38+ } else {
3339 self.index -= 1;
3440 }
3541 }
M src/main.rs View
@@ -9,6 +9,7 @@ use rmptui::connection::Connection;
99 use rmptui::event::Event;
1010 use rmptui::event::EventHandler;
1111 use rmptui::handler;
12+use rmptui::song::RSong;
1213 use rmptui::tui;
1314 use std::io;
1415
@@ -23,41 +24,52 @@ pub type Result<T> = core::result::Result<T, Error>;
2324 pub type Error = Box<dyn std::error::Error>;
2425
2526 fn main() -> AppResult<()> {
26− // let args = Args::parse();
27−
28− // if args.no_tui {
29− // handle_tui()?;
30− // } else {
31− // match args.command {
32− // Command::Volume { vol } => {
33− // conn.set_volume(vol);
34− // }
35− // Command::Dmenu => conn.play_dmenu().unwrap(),
36− // Command::Fzf => conn.play_fzf().unwrap(),
37− // Command::Status => conn.status(),
38− // Command::Pause => conn.pause(),
39− // Command::Toggle => conn.toggle_pause(),
40− // };
41− // }
42−
43− let backend = CrosstermBackend::new(io::stderr());
44− let terminal = Terminal::new(backend)?;
27+ let args = Args::parse();
4528 let mut app = App::builder("127.0.0.1:6600")?;
46− let events = EventHandler::new(250);
29+
30+ if !args.tui {
31+ handle_tui(&mut app)?;
32+ } else {
33+ match args.command {
34+ Some(Command::Dmenu) => app.conn.play_dmenu()?,
35+ Some(Command::Fzf) => app.conn.play_fzf().unwrap(),
36+ Some(Command::Status) => app.conn.status(),
37+ Some(Command::Pause) => app.conn.pause(),
38+ Some(Command::Toggle) => app.conn.toggle_pause(),
39+ _ => {
40+ let mut vec: Vec<RSong> = Vec::new();
41+ for filename in app.conn.songs_filenames {
42+ let song = RSong::new(&mut app.conn.conn, filename);
43+ vec.push(song);
44+ }
45+ println!("{:#?}", vec);
46+
47+ }
48+ }
49+ }
50+ Ok(())
51+}
52+
53+pub fn handle_tui(app: &mut App) -> AppResult<()> {
54+ let backend = CrosstermBackend::new(io::stderr());
55+ let terminal = Terminal::new(backend)?;
56+ let events = EventHandler::new(1000);
4757
4858 let mut tui = tui::Tui::new(terminal, events);
4959 tui.init()?;
5060
61+ // update the directory
62+ app.browser.update_directory(&mut app.conn).unwrap();
63+
5164 while app.running {
52− tui.draw(&mut app)?;
65+ tui.draw(app)?;
5366 match tui.events.next()? {
5467 Event::Tick => app.tick(),
55− Event::Key(key_event) => handler::handle_key_events(key_event, &mut app)?,
68+ Event::Key(key_event) => handler::handle_key_events(key_event, app)?,
5669 Event::Mouse(_) => {}
5770 Event::Resize(_, _) => {}
5871 }
5972 }
6073
61−
6274 Ok(())
6375 }
A src/song.rs View
@@ -0,0 +1,67 @@
1+use mpd::{Client, Song};
2+
3+#[derive(Debug)]
4+#[derive(Clone )]
5+pub struct RSong {
6+ pub file: String,
7+ pub artist: Option<String>,
8+ pub title: Option<String>,
9+ pub duration: Option<u32>,
10+ pub last_mod: Option<String>,
11+ pub name: Option<String>,
12+ pub place: Option<String>,
13+ pub range: Option<String>,
14+ pub tags: Vec<(String, String)>,
15+}
16+
17+impl RSong {
18+ pub fn new(c: &mut Client, filename: String) -> Self {
19+ let mut s = RSong {
20+ file: filename.clone(),
21+ artist: None,
22+ title: None,
23+ duration: None,
24+ last_mod: None,
25+ name: None,
26+ place: None,
27+ range: None,
28+ tags: vec![],
29+ };
30+
31+ // Dummy song
32+
33+ let song = Song {
34+ file: filename.clone(),
35+ artist: None,
36+ title: None,
37+ duration: None,
38+ last_mod: None,
39+ name: None,
40+ place: None,
41+ range: None,
42+ tags: vec![("".to_string(), "".to_string())],
43+ };
44+
45+ for (k, v) in (c.readcomments(song).unwrap()).flatten() {
46+ if k.to_lowercase().contains("artist") {
47+ s.artist = Some(v);
48+ } else if k.to_lowercase().contains("title") {
49+ s.title = Some(v);
50+ } else if k.to_lowercase().contains("duration") {
51+ s.duration = Some(v.parse::<u32>().unwrap());
52+ } else if k.to_lowercase().contains("lastmod") {
53+ s.last_mod = Some(v);
54+ } else if k.to_lowercase().contains("name") {
55+ s.name = Some(v);
56+ } else if k.to_lowercase().contains("place") {
57+ s.place = Some(v);
58+ } else if k.to_lowercase().contains("range") {
59+ s.range = Some(v);
60+ } else {
61+ s.tags.push((k, v));
62+ }
63+ }
64+
65+ s
66+ }
67+}
M src/ui.rs View
@@ -14,7 +14,7 @@ pub fn render(app: &mut App, frame: &mut Frame) {
1414 // Layout
1515 let layout = Layout::default()
1616 .direction(Direction::Vertical)
17− .constraints(vec![Constraint::Percentage(93), Constraint::Percentage(7)])
17+ .constraints(vec![Constraint::Percentage(93), Constraint::Min(3)])
1818 .split(frame.size());
1919
2020 match app.selected_tab {
@@ -52,7 +52,12 @@ fn draw_directory_browser(frame: &mut Frame, app: &mut App, size: Rect) {
5252 )
5353 .borders(Borders::ALL),
5454 )
55− .highlight_style(Style::new().add_modifier(Modifier::REVERSED))
55+ .highlight_style(
56+ Style::new()
57+ .fg(Color::Cyan)
58+ .bg(Color::Black)
59+ .add_modifier(Modifier::REVERSED),
60+ )
5661 .highlight_symbol(">>")
5762 .repeat_highlight_symbol(true)
5863 .scroll_padding(20);
@@ -72,7 +77,12 @@ fn draw_queue(frame: &mut Frame, app: &mut App, size: Rect) {
7277 );
7378 let list = List::new(app.queue_list.list.clone())
7479 .block(title.borders(Borders::ALL))
75− .highlight_style(Style::new().add_modifier(Modifier::REVERSED))
80+ .highlight_style(
81+ Style::new()
82+ .fg(Color::Cyan)
83+ .bg(Color::Black)
84+ .add_modifier(Modifier::REVERSED),
85+ )
7686 .highlight_symbol(">>")
7787 .repeat_highlight_symbol(true);
7888
@@ -93,7 +103,12 @@ fn draw_playlists(frame: &mut Frame, app: &mut App, size: Rect) {
93103
94104 let list = List::new(app.pl_list.list.clone())
95105 .block(title.borders(Borders::ALL))
96− .highlight_style(Style::new().add_modifier(Modifier::REVERSED))
106+ .highlight_style(
107+ Style::new()
108+ .fg(Color::Cyan)
109+ .bg(Color::Black)
110+ .add_modifier(Modifier::REVERSED),
111+ )
97112 .highlight_symbol(">>")
98113 .repeat_highlight_symbol(true);
99114
@@ -103,39 +118,65 @@ fn draw_playlists(frame: &mut Frame, app: &mut App, size: Rect) {
103118
104119 /// Draws Progress Bar
105120 fn draw_progress_bar(frame: &mut Frame, app: &mut App, size: Rect) {
121+ // Get the current playing song
106122 let song = app
107123 .conn
108124 .now_playing()
109125 .unwrap()
110126 .unwrap_or_else(|| "No Title Found".to_string());
111127
112− let state = &app.conn.state;
113− // let (elapsed, total) = app.conn.conn.status().unwrap().time.unwrap_or_default();
128+ // Get the current playing state
129+ let mut state: String = String::new();
130+ if !app.queue_list.list.is_empty() {
131+ state = app.conn.state.clone();
132+ state.push(':');
133+ }
134+
135+ // Get the current modes
136+ let mut modes_bottom: String = String::new();
137+ // we do this to check if at least one mode is enabled so we can push "[]"
138+ if app.conn.repeat | app.conn.random {
139+ modes_bottom.push('r');
140+ }
114141
142+ if !modes_bottom.is_empty() {
143+ modes_bottom.clear();
144+ modes_bottom.push('[');
145+ if app.conn.repeat {
146+ modes_bottom.push('r');
147+ }
148+ if app.conn.random {
149+ modes_bottom.push('z');
150+ }
151+ modes_bottom.push(']');
152+ };
153+
154+
155+ // get the duration
156+ let duration = if app.conn.total_duration.as_secs() != 0 {
157+ format!(
158+ "[{}/{}]",
159+ humantime::format_duration(app.conn.elapsed),
160+ humantime::format_duration(app.conn.total_duration)
161+ )
162+ } else {
163+ "".to_string()
164+ };
165+
166+ // Define the title
115167 let title = Block::default()
116− .title(Title::from(format!("{}: ", state).red().bold()))
168+ .title(Title::from(format!("{}", state).red().bold()))
117169 .title(Title::from(song.green().bold()))
118− .title(
119− Title::from(
120− format!(
121− "{}/{}",
122− app.conn.elapsed.as_secs(),
123− app.conn.total_duration.as_secs()
124− )
125− .cyan()
126− .bold(),
127− )
128− .alignment(Alignment::Right),
129− )
170+ .title(Title::from(duration.cyan().bold()).alignment(Alignment::Right))
171+ .title(Title::from(format!("{}", modes_bottom)).position(block::Position::Bottom))
130172 .borders(Borders::ALL);
131173
132− // .title(Title::from(app.conn.conn.status().unwrap_or_default().volume.to_string().yellow())).title_alignment(Alignment::Right);
133174 let progress_bar = LineGauge::default()
134175 .block(title.borders(Borders::ALL))
135176 .gauge_style(
136177 Style::default()
137− .fg(Color::LightBlue)
138− .bg(Color::Gray)
178+ .fg(Color::Blue)
179+ .bg(Color::Black)
139180 .add_modifier(Modifier::BOLD),
140181 )
141182 .line_set(symbols::line::THICK)