Scrolling through list added, splash screen modified.

This commit is contained in:
Michael Wain 2025-02-17 19:23:30 +03:00
parent 6b8b4ef355
commit 3bd8f1c75d
7 changed files with 316 additions and 236 deletions

78
Cargo.lock generated
View File

@ -301,6 +301,37 @@ dependencies = [
"powerfmt", "powerfmt",
] ]
[[package]]
name = "derive_builder"
version = "0.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947"
dependencies = [
"derive_builder_macro",
]
[[package]]
name = "derive_builder_core"
version = "0.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8"
dependencies = [
"darling",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "derive_builder_macro"
version = "0.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c"
dependencies = [
"derive_builder_core",
"syn",
]
[[package]] [[package]]
name = "dirs" name = "dirs"
version = "6.0.0" version = "6.0.0"
@ -405,6 +436,12 @@ version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f"
[[package]]
name = "font8x8"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "875488b8711a968268c7cf5d139578713097ca4635a76044e8fe8eedf831d07e"
[[package]] [[package]]
name = "foreign-types" name = "foreign-types"
version = "0.3.2" version = "0.3.2"
@ -928,6 +965,15 @@ dependencies = [
"either", "either",
] ]
[[package]]
name = "itertools"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
dependencies = [
"either",
]
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "1.0.14" version = "1.0.14"
@ -936,8 +982,8 @@ checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
[[package]] [[package]]
name = "itunesdb" name = "itunesdb"
version = "0.1.50" version = "0.1.51"
source = "git+https://gitea.awain.net/alterwain/ITunesDB.git#2c38d91cd89908b20a2b08da1b3202dacf5d1f9b" source = "git+https://gitea.awain.net/alterwain/ITunesDB.git#ed639f96d59b6777114551e8c3054f2a0ac9c7da"
dependencies = [ dependencies = [
"bincode", "bincode",
"env_logger", "env_logger",
@ -1047,9 +1093,11 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"soundcloud", "soundcloud",
"throbber-widgets-tui",
"tokio", "tokio",
"tokio-util", "tokio-util",
"toml", "toml",
"tui-big-text",
] ]
[[package]] [[package]]
@ -1344,7 +1392,7 @@ dependencies = [
"crossterm", "crossterm",
"indoc", "indoc",
"instability", "instability",
"itertools", "itertools 0.13.0",
"lru", "lru",
"paste", "paste",
"strum", "strum",
@ -1893,6 +1941,16 @@ dependencies = [
"once_cell", "once_cell",
] ]
[[package]]
name = "throbber-widgets-tui"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d36b5738d666a2b4c91b7c24998a8588db724b3107258343ebf8824bf55b06d"
dependencies = [
"rand",
"ratatui",
]
[[package]] [[package]]
name = "time" name = "time"
version = "0.3.37" version = "0.3.37"
@ -2094,6 +2152,18 @@ version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
[[package]]
name = "tui-big-text"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a97cefa9f1425ab6146db2961241cec86845d11105b5dd6bb504294b0cdd21af"
dependencies = [
"derive_builder",
"font8x8",
"itertools 0.14.0",
"ratatui",
]
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.16" version = "1.0.16"
@ -2112,7 +2182,7 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf"
dependencies = [ dependencies = [
"itertools", "itertools 0.13.0",
"unicode-segmentation", "unicode-segmentation",
"unicode-width 0.1.14", "unicode-width 0.1.14",
] ]

View File

@ -20,7 +20,9 @@ futures = "0.3"
tokio = { version = "1", features = ["full"] } tokio = { version = "1", features = ["full"] }
tokio-util = { version = "0.7.12", features = ["codec"] } tokio-util = { version = "0.7.12", features = ["codec"] }
soundcloud = { version = "0.1.8", git = "https://gitea.awain.net/alterwain/soundcloud_api.git" } soundcloud = { version = "0.1.8", git = "https://gitea.awain.net/alterwain/soundcloud_api.git" }
itunesdb = { version = "0.1.50", git = "https://gitea.awain.net/alterwain/ITunesDB.git" } itunesdb = { version = "0.1.51", git = "https://gitea.awain.net/alterwain/ITunesDB.git" }
puremp3 = "0.1.0" puremp3 = "0.1.0"
mp3-duration = "0.1.10" mp3-duration = "0.1.10"
rand = "0.8.5" rand = "0.8.5"
tui-big-text = "0.7.1"
throbber-widgets-tui = "0.8.0"

View File

@ -4,7 +4,9 @@ pub mod table {
use ratatui::widgets::{Block, Borders, Row, Table}; use ratatui::widgets::{Block, Borders, Row, Table};
use ratatui::Frame; use ratatui::Frame;
#[derive(Default)]
pub struct SmartTable { pub struct SmartTable {
is_checked: bool,
header: Vec<String>, header: Vec<String>,
data: Vec<Vec<String>>, data: Vec<Vec<String>>,
constraints: Vec<Constraint>, constraints: Vec<Constraint>,
@ -15,6 +17,7 @@ pub mod table {
impl SmartTable { impl SmartTable {
pub fn new(header: Vec<String>, constraints: Vec<Constraint>) -> Self { pub fn new(header: Vec<String>, constraints: Vec<Constraint>) -> Self {
Self { Self {
is_checked: true,
header, header,
data: Vec::new(), data: Vec::new(),
constraints, constraints,
@ -23,6 +26,10 @@ pub mod table {
} }
} }
pub fn set_checked(&mut self, checked: bool) {
self.is_checked = checked;
}
pub fn set_data(&mut self, data: Vec<Vec<String>>) { pub fn set_data(&mut self, data: Vec<Vec<String>>) {
self.data = data; self.data = data;
} }
@ -49,14 +56,22 @@ pub mod table {
for (i, entry) in self.data.iter().enumerate() { for (i, entry) in self.data.iter().enumerate() {
v.push( v.push(
Row::new(entry.clone()).style(if self.selected_row as usize == i { Row::new(entry.clone()).style(if self.selected_row as usize == i {
Style::default().bg(Color::LightBlue).fg(Color::White) Style::default()
.bg(if self.is_checked {
Color::LightBlue
} else {
Color::Gray
})
.fg(Color::White)
} else { } else {
Style::default() Style::default()
}), }),
); );
} }
if self.selected_row as usize > area.rows().count() - 4 { if self.selected_row as usize > area.rows().count() - 4
&& self.selected_row - ((area.rows().count() - 4) as i32) >= 0
{
v = v[(self.selected_row as usize - (area.rows().count() - 4))..].to_vec(); v = v[(self.selected_row as usize - (area.rows().count() - 4))..].to_vec();
} }

View File

@ -45,7 +45,7 @@ impl AppScreen for FileSystem {
} }
} }
fn render(&self, frame: &mut ratatui::Frame, theme: &Theme) { fn render(&self, frame: &mut Frame, _theme: &Theme) {
let chunks = Layout::default() let chunks = Layout::default()
.direction(Direction::Vertical) .direction(Direction::Vertical)
.constraints([ .constraints([

View File

@ -9,7 +9,7 @@ use crossterm::{
execute, execute,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
}; };
use futures::StreamExt; use futures::{StreamExt, TryStreamExt};
use loading_screen::LoadingScreen; use loading_screen::LoadingScreen;
use main_screen::MainScreen; use main_screen::MainScreen;
use ratatui::{ use ratatui::{
@ -18,6 +18,7 @@ use ratatui::{
Frame, Terminal, Frame, Terminal,
}; };
use screen::AppScreen; use screen::AppScreen;
use std::time::Duration;
use std::{collections::HashMap, error::Error, io}; use std::{collections::HashMap, error::Error, io};
use sync::AppEvent; use sync::AppEvent;
use tokio::sync::mpsc::{self, Receiver, UnboundedSender}; use tokio::sync::mpsc::{self, Receiver, UnboundedSender};
@ -134,6 +135,9 @@ impl App {
_ => {} _ => {}
} }
} }
_ = tokio::time::sleep(Duration::from_millis(200)) => {
}
} }
} }

View File

@ -4,22 +4,21 @@ use ratatui::{
layout::{Constraint, Direction, Layout, Rect}, layout::{Constraint, Direction, Layout, Rect},
style::{Color, Modifier, Style, Stylize}, style::{Color, Modifier, Style, Stylize},
text::{Line, Span}, text::{Line, Span},
widgets::{Block, Borders, Paragraph, Row, Table, Tabs}, widgets::{Block, Borders, Paragraph, Tabs},
Frame, Frame,
}; };
use soundcloud::sobjects::{CloudPlaylist, CloudPlaylists}; use soundcloud::sobjects::{CloudPlaylist, CloudPlaylists};
use tokio::sync::mpsc::UnboundedSender; use tokio::sync::mpsc::UnboundedSender;
use crate::component::table::SmartTable;
use crate::sync::DBPlaylist; use crate::sync::DBPlaylist;
use crate::{screen::AppScreen, sync::AppEvent, theme::Theme, AppState}; use crate::{screen::AppScreen, sync::AppEvent, theme::Theme, AppState};
pub struct MainScreen { pub struct MainScreen {
mode: bool, mode: bool,
selected_tab: i8, selected_tab: i8,
selected_playlist: i32, pl_table: SmartTable,
selected_song: i32, song_table: SmartTable,
max_pls: i32,
max_songs: i32,
tab_titles: Vec<String>, tab_titles: Vec<String>,
soundcloud: Option<Vec<CloudPlaylist>>, soundcloud: Option<Vec<CloudPlaylist>>,
playlists: Option<Vec<DBPlaylist>>, playlists: Option<Vec<DBPlaylist>>,
@ -33,17 +32,18 @@ impl AppScreen for MainScreen {
KeyCode::Left => self.previous_tab(), KeyCode::Left => self.previous_tab(),
KeyCode::Up => self.previous_row(), KeyCode::Up => self.previous_row(),
KeyCode::Down => self.next_row(), KeyCode::Down => self.next_row(),
KeyCode::F(6) => self.download_row(), KeyCode::F(5) => self.download_row(),
KeyCode::Tab => self.switch_mode(), KeyCode::Tab => self.switch_mode(),
KeyCode::F(2) => { KeyCode::F(4) => {
self.sender let _ = self
.sender
.send(AppEvent::SwitchScreen(AppState::FileSystem)); .send(AppEvent::SwitchScreen(AppState::FileSystem));
} }
_ => {} _ => {}
} }
} }
fn render(&self, frame: &mut Frame, theme: &Theme) { fn render(&self, frame: &mut Frame, _theme: &Theme) {
let chunks = Layout::default() let chunks = Layout::default()
.direction(Direction::Vertical) .direction(Direction::Vertical)
.constraints([ .constraints([
@ -76,9 +76,11 @@ impl AppScreen for MainScreen {
let status_bar = Paragraph::new(Line::from(vec![ let status_bar = Paragraph::new(Line::from(vec![
"◄ ► to change tab".bold(), "◄ ► to change tab".bold(),
" | ".dark_gray(), " | ".dark_gray(),
"<F5> SAVE FS".bold(), "<TAB> SWITCH PANEL".bold(),
" | ".dark_gray(), " | ".dark_gray(),
"<F6> DL".bold(), "<F4> FS MODE".bold(),
" | ".dark_gray(),
"<F5> DOWNLOAD".bold(),
" | ".dark_gray(), " | ".dark_gray(),
"<F8> DEL".bold(), "<F8> DEL".bold(),
" | ".dark_gray(), " | ".dark_gray(),
@ -97,10 +99,8 @@ impl MainScreen {
pub fn new(sender: UnboundedSender<AppEvent>) -> Self { pub fn new(sender: UnboundedSender<AppEvent>) -> Self {
MainScreen { MainScreen {
mode: false, mode: false,
selected_playlist: 0, pl_table: SmartTable::default(),
selected_song: 0, song_table: SmartTable::default(),
max_pls: 0,
max_songs: 0,
soundcloud: None, soundcloud: None,
playlists: None, playlists: None,
selected_tab: 0, selected_tab: 0,
@ -114,42 +114,14 @@ impl MainScreen {
} }
} }
fn update_max_rows(&mut self) {
self.selected_song = 0;
self.selected_playlist = 0;
self.max_songs = 0;
self.max_pls = match self.selected_tab {
1 => self.soundcloud.as_deref().unwrap_or(&[]).len(),
2 => self.playlists.as_deref().unwrap_or(&[]).len(),
_ => 0,
}
.try_into()
.unwrap();
self.update_max_songs();
}
fn update_max_songs(&mut self) {
if self.max_pls > 0 {
self.max_songs = match self.selected_tab {
1 => self
.soundcloud
.as_deref()
.unwrap()
.get(self.selected_playlist as usize)
.unwrap()
.tracks
.len(),
_ => 0,
}
.try_into()
.unwrap();
self.selected_song = 0;
}
}
fn switch_mode(&mut self) { fn switch_mode(&mut self) {
self.mode = !self.mode; self.set_mode(!self.mode);
}
fn set_mode(&mut self, mode: bool) {
self.mode = mode;
self.pl_table.set_checked(!self.mode);
self.song_table.set_checked(self.mode);
} }
fn next_tab(&mut self) { fn next_tab(&mut self) {
@ -157,31 +129,30 @@ impl MainScreen {
self.selected_tab + 1, self.selected_tab + 1,
(self.tab_titles.len() - 1).try_into().unwrap(), (self.tab_titles.len() - 1).try_into().unwrap(),
); );
self.update_max_rows(); self.update_tables();
} }
fn previous_tab(&mut self) { fn previous_tab(&mut self) {
self.selected_tab = std::cmp::max(0, self.selected_tab - 1); self.selected_tab = std::cmp::max(0, self.selected_tab - 1);
self.update_max_rows(); self.update_tables();
} }
fn previous_row(&mut self) { fn previous_row(&mut self) {
match self.mode { match self.mode {
true => self.selected_song = std::cmp::max(0, self.selected_song - 1), true => self.song_table.previous_row(),
false => { false => {
self.selected_playlist = std::cmp::max(0, self.selected_playlist - 1); self.pl_table.previous_row();
self.update_max_songs(); self.update_songs();
} }
} }
} }
fn next_row(&mut self) { fn next_row(&mut self) {
match self.mode { match self.mode {
true => self.selected_song = std::cmp::min(self.selected_song + 1, self.max_songs - 1), true => self.song_table.next_row(),
false => { false => {
self.selected_playlist = self.pl_table.next_row();
std::cmp::min(self.selected_playlist + 1, self.max_pls - 1); self.update_songs();
self.update_max_songs();
} }
} }
} }
@ -193,7 +164,7 @@ impl MainScreen {
.soundcloud .soundcloud
.as_ref() .as_ref()
.unwrap() .unwrap()
.get(self.selected_playlist as usize) .get(self.pl_table.selected_row())
.unwrap() .unwrap()
.clone(); .clone();
let _ = self.sender.send(AppEvent::DownloadPlaylist(playlist)); let _ = self.sender.send(AppEvent::DownloadPlaylist(playlist));
@ -203,108 +174,109 @@ impl MainScreen {
pub fn set_soundcloud_playlists(&mut self, pl: CloudPlaylists) { pub fn set_soundcloud_playlists(&mut self, pl: CloudPlaylists) {
self.soundcloud = Some(pl.collection); self.soundcloud = Some(pl.collection);
if self.selected_tab == 1 { if self.selected_tab == 1 {
self.update_max_rows(); self.update_tables();
} }
} }
pub fn set_itunes(&mut self, pl: Vec<DBPlaylist>) { pub fn set_itunes(&mut self, pl: Vec<DBPlaylist>) {
self.playlists = Some(pl); self.playlists = Some(pl);
if self.selected_tab == 2 { if self.selected_tab == 2 {
self.update_max_rows(); self.update_tables();
} }
} }
fn render_tab(&self, frame: &mut Frame, area: Rect) { fn update_tables(&mut self) {
let rows = match self.selected_tab { self.set_mode(false);
1 => {
// SC
let mut v = Vec::new();
v.push(
Row::new(vec!["Id", "Title", "Songs Count", "Date", "IS"])
.style(Style::default().fg(Color::Gray)),
);
if let Some(s) = &self.soundcloud {
for (i, playlist) in s.iter().enumerate() {
let date: DateTime<Utc> = playlist.created_at.parse().unwrap();
let mut row = Row::new(vec![
playlist.id.to_string(),
playlist.title.clone(),
[playlist.track_count.to_string(), " songs".to_string()].concat(),
format!("{}", date.format("%Y-%m-%d %H:%M")),
"NO".to_string(),
]);
if self.selected_playlist == i as i32 {
row = row.style(Style::default().bg(Color::LightBlue).fg(Color::White));
}
v.push(row);
}
}
v
}
2 => {
// local
let mut v = Vec::new();
v.push(
Row::new(vec!["Id", "Title", "Songs Count", "Date", "IS"])
.style(Style::default().fg(Color::Gray)),
);
if let Some(s) = &self.playlists {
for (i, playlist) in s.iter().enumerate() {
let date = Utc.timestamp_millis_opt(playlist.timestamp as i64).unwrap();
let mut row = Row::new(vec![
playlist.id.to_string(),
"".to_string(),
playlist.tracks.len().to_string(),
format!("{}", date.format("%Y-%m-%d %H:%M")),
"YES".to_string(),
]);
if self.selected_playlist == i as i32 {
row = row.style(Style::default().bg(Color::LightBlue).fg(Color::White));
}
v.push(row);
}
}
v
}
_ => Vec::new(),
};
let chunks = Layout::default() self.pl_table = SmartTable::new(
.direction(Direction::Vertical) ["Id", "Title", "Songs Count", "Date", "IS"]
.constraints([ .iter_mut()
Constraint::Percentage(30), // Playlists .map(|s| s.to_string())
Constraint::Min(0), // Tracks .collect(),
]) [
.split(area);
// Create the table
let table = Table::new(
rows,
&[
Constraint::Length(3), // ID column Constraint::Length(3), // ID column
Constraint::Percentage(50), // Playlist name column Constraint::Percentage(50), // Playlist name column
Constraint::Percentage(20), // Song count column Constraint::Percentage(20), // Song count column
Constraint::Percentage(30), Constraint::Percentage(30),
Constraint::Length(2), Constraint::Length(2),
], ]
) .to_vec(),
.block(Block::default().borders(Borders::ALL).title(" Playlists "))
.style(Style::default().fg(Color::Black));
frame.render_widget(table, chunks[0]);
let mut rows = match self.selected_tab {
1 => {
// sc
let mut v = Vec::new();
v.push(
Row::new(vec!["Id", "Title", "Artist", "Duration", "Genre"])
.style(Style::default().fg(Color::Gray)),
); );
let data = match self.selected_tab {
1 => {
if let Some(sc) = &self.soundcloud {
sc.iter()
.map(|playlist| {
let date: DateTime<Utc> = playlist.created_at.parse().unwrap();
vec![
playlist.id.to_string(),
playlist.title.clone(),
[playlist.track_count.to_string(), " songs".to_string()].concat(),
format!("{}", date.format("%Y-%m-%d %H:%M")),
"NO".to_string(),
]
})
.collect::<Vec<Vec<String>>>()
} else {
Vec::new()
}
}
2 => {
if let Some(it) = &self.playlists {
it.iter()
.map(|playlist| {
let date = Utc.timestamp_millis_opt(playlist.timestamp as i64).unwrap();
vec![
playlist.id.to_string(),
"".to_string(),
playlist.tracks.len().to_string(),
format!("{}", date.format("%Y-%m-%d %H:%M")),
"YES".to_string(),
]
})
.collect::<Vec<Vec<String>>>()
} else {
Vec::new()
}
}
_ => {
self.pl_table = SmartTable::default();
Vec::new()
}
};
self.pl_table.set_data(data);
self.pl_table.set_title("Playlists".to_string());
self.update_songs();
}
fn update_songs(&mut self) {
let constraints = [
Constraint::Length(3), // ID column
Constraint::Percentage(50), // Playlist name column
Constraint::Percentage(20), // Song count column
Constraint::Length(5),
Constraint::Min(0),
]
.to_vec();
match self.selected_tab {
1 => {
self.song_table = SmartTable::new(
["Id", "Title", "Artist", "Duration", "Genre"]
.iter_mut()
.map(|s| s.to_string())
.collect(),
constraints,
);
self.set_mode(self.mode);
if let Some(pls) = &self.soundcloud { if let Some(pls) = &self.soundcloud {
let s = &pls.get(self.selected_playlist as usize).unwrap().tracks; let s = &pls.get(self.pl_table.selected_row()).unwrap().tracks;
for (i, track) in s.iter().enumerate() { let data = s
let mut row = Row::new(vec![ .iter()
.map(|track| {
vec![
track.id.to_string(), track.id.to_string(),
track.title.as_deref().unwrap().to_string(), track.title.as_deref().unwrap().to_string(),
track track
@ -315,61 +287,61 @@ impl MainScreen {
.unwrap_or(track.user.as_ref().unwrap().permalink.clone()), .unwrap_or(track.user.as_ref().unwrap().permalink.clone()),
track.duration.unwrap_or(0).to_string(), track.duration.unwrap_or(0).to_string(),
track.genre.as_ref().unwrap_or(&String::new()).to_string(), track.genre.as_ref().unwrap_or(&String::new()).to_string(),
]); ]
if self.selected_song == i as i32 { })
row = row.style(Style::default().bg(Color::LightBlue).fg(Color::White)); .collect::<Vec<Vec<String>>>();
self.song_table.set_data(data);
} }
v.push(row); self.song_table.set_title(" Songs ".to_string());
}
}
v
} }
2 => { 2 => {
// local self.song_table = SmartTable::new(
let mut v = Vec::new(); ["Id", "Title", "Artist", "Bitrate", "Genre"]
v.push( .iter_mut()
Row::new(vec!["Id", "Title", "Artist", "Bitrate", "Genre"]) .map(|s| s.to_string())
.style(Style::default().fg(Color::Gray)), .collect(),
constraints,
); );
self.set_mode(self.mode);
if let Some(pls) = &self.playlists { if let Some(pls) = &self.playlists {
let s = &pls.get(self.selected_playlist as usize).unwrap().tracks; let s = &pls.get(self.pl_table.selected_row()).unwrap().tracks;
for (i, track) in s.iter().enumerate() { let data = s
let mut row = Row::new(vec![ .iter()
.map(|track| {
vec![
track.data.unique_id.to_string(), track.data.unique_id.to_string(),
track.get_title(), track.get_title(),
track.get_location(), track.get_location(),
track.data.bitrate.to_string(), track.data.bitrate.to_string(),
track.get_genre(), track.get_genre(),
]); ]
if self.selected_song == i as i32 { })
row = row.style(Style::default().bg(Color::LightBlue).fg(Color::White)); .collect::<Vec<Vec<String>>>();
}
v.push(row);
}
}
v
}
_ => Vec::new(),
};
if chunks[1].rows().count() <= self.selected_song as usize { self.song_table.set_data(data);
rows = rows[self.selected_song as usize..].to_vec();
} }
// Create the table self.song_table.set_title(" Songs ".to_string());
let table = Table::new( }
rows, _ => {
&[ self.song_table = SmartTable::default();
Constraint::Length(3), // ID column self.set_mode(self.mode);
Constraint::Percentage(50), // Playlist name column }
Constraint::Percentage(20), // Song count column }
Constraint::Length(5), }
Constraint::Min(0),
],
)
.block(Block::default().borders(Borders::ALL).title(" Songs "))
.style(Style::default().fg(Color::Black));
frame.render_widget(table, chunks[1]); fn render_tab(&self, frame: &mut Frame, area: Rect) {
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Percentage(30), // Playlists
Constraint::Min(0), // Tracks
])
.split(area);
self.pl_table.render(frame, chunks[0]);
self.song_table.render(frame, chunks[1]);
} }
} }

View File

@ -1,13 +1,14 @@
use crate::{screen::AppScreen, theme::Theme};
use color_eyre::owo_colors::OwoColorize; use color_eyre::owo_colors::OwoColorize;
use ratatui::layout::{Constraint, Direction, Flex, Layout};
use ratatui::widgets::Paragraph;
use ratatui::{ use ratatui::{
style::{Style, Stylize}, style::{Style, Stylize},
symbols::border, text::Line,
text::{Line, Text},
widgets::{Block, Paragraph},
Frame, Frame,
}; };
use throbber_widgets_tui::{ThrobberState, BOX_DRAWING};
use crate::{screen::AppScreen, theme::Theme}; use tui_big_text::{BigText, PixelSize};
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct WaitScreen {} pub struct WaitScreen {}
@ -16,21 +17,37 @@ impl AppScreen for WaitScreen {
fn handle_key_event(&mut self, key_event: crossterm::event::KeyEvent) {} fn handle_key_event(&mut self, key_event: crossterm::event::KeyEvent) {}
fn render(&self, frame: &mut Frame, theme: &Theme) { fn render(&self, frame: &mut Frame, theme: &Theme) {
let title = Line::from(" Lyrica ".bold()); let chunks = Layout::default()
let instructions = Line::from(vec![" Quit ".into(), "<Q> ".red().bold()]); .direction(Direction::Vertical)
let block = Block::bordered() .constraints([Constraint::Percentage(33); 3])
.title(title.centered()) .split(frame.area());
.title_bottom(instructions.centered())
.border_set(border::ROUNDED);
let counter_text = Text::from(vec![Line::from(vec!["Searching for iPod...".into()])]); let simple = throbber_widgets_tui::Throbber::default()
.label("Searching for your iPod")
.throbber_set(BOX_DRAWING);
let par = Paragraph::new(counter_text) let bottom =
.style(Style::new().bg(theme.background())) Paragraph::new(vec![Line::from(vec![" Quit ".into(), "<Q> ".red().bold()])]).centered();
let bottom_l =
Layout::vertical([Constraint::Min(0), Constraint::Length(1)]).split(chunks[2]);
let [throbber_l] = Layout::horizontal([Constraint::Length(
simple.to_line(&ThrobberState::default()).width() as u16,
)])
.flex(Flex::Center)
.areas(bottom_l[0]);
frame.render_widget(simple, throbber_l);
frame.render_widget(bottom, bottom_l[1]);
let title = BigText::builder()
.pixel_size(PixelSize::Full)
.style(Style::new().blue())
.lines(vec!["Lyrica".light_blue().into()])
.centered() .centered()
.block(block); .build();
frame.render_widget(par, frame.area()); frame.render_widget(title, chunks[1]);
} }
fn as_any(&mut self) -> &mut dyn std::any::Any { fn as_any(&mut self) -> &mut dyn std::any::Any {