feat: add gRPC client with config-based server address and album support

- Add tonic/prost gRPC client connecting to music-agregator service
- Add config.yaml for configurable server host/port
- Add build.rs for proto compilation from music-agregator
- Update Artist/Album models to match proto with MonitorState enum
- Convert album list from GetArtists response
- Fix album click selection with correct layout offsets
- Improve monitor state icons for better visibility
This commit is contained in:
Alexander
2026-05-08 23:10:15 +02:00
parent 620bd374de
commit e77e854d2e
14 changed files with 1812 additions and 50 deletions
+31 -11
View File
@@ -8,7 +8,7 @@ use ratatui::{
widgets::{List, ListItem, ListState, Paragraph},
};
use crate::data::{Album, AlbumStatus, Artist, Track};
use crate::data::{Album, AlbumStatus, Artist, MonitorState, Track};
use crate::theme;
use crate::ui::pane::{Pane, section_divider};
use crate::ui::progress_bar::progress_bar;
@@ -201,6 +201,20 @@ fn status_icon(status: AlbumStatus, monitored: bool) -> (char, Style) {
}
}
fn monitor_state_icon(state: MonitorState, status: AlbumStatus) -> (char, Style) {
match state {
MonitorState::Monitored => match status {
AlbumStatus::Complete => ('✓', Style::default().fg(theme::GREEN)),
AlbumStatus::Partial => ('◐', Style::default().fg(theme::YELLOW)),
AlbumStatus::Wanted => ('!', Style::default().fg(theme::RED)),
AlbumStatus::Unmonitored => ('-', Style::default().fg(theme::GRAY)),
},
MonitorState::Unmonitored => ('-', Style::default().fg(theme::GRAY)),
MonitorState::Excluded => ('x', Style::default().fg(theme::RED)),
MonitorState::Unspecified => ('?', Style::default().fg(theme::GRAY)),
}
}
fn track_icon(have: bool) -> (char, Style) {
if have {
('✓', Style::default().fg(theme::GREEN))
@@ -267,15 +281,12 @@ fn render_artists_pane(frame: &mut Frame, area: Rect, state: &mut LibraryState)
.iter()
.map(|artist| {
let status = artist_status(artist);
let (icon_char, icon_style) = status_icon(status, artist.monitored);
let (icon_char, icon_style) = monitor_state_icon(artist.monitor_state, status);
let total: u16 = artist.albums.iter().map(|a| a.total).sum();
let have: u16 = artist.albums.iter().map(|a| a.have).sum();
let mut name_text = artist.name.clone();
if !artist.monitored {
name_text.push_str(" ·unm");
}
let count_str = format!("{}/{}", have, total);
let name_width = inner.width as usize - 2 - count_str.len() - 2;
@@ -399,18 +410,27 @@ fn render_artist_header(frame: &mut Frame, area: Rect, artist: &Artist) {
let have: u16 = artist.albums.iter().map(|a| a.have).sum();
let total: u16 = artist.albums.iter().map(|a| a.total).sum();
let (status_icon, status_text, status_style) = if artist.monitored {
(
let (status_icon, status_text, status_style) = match artist.monitor_state {
MonitorState::Monitored => (
Span::styled("", Style::default().fg(theme::GREEN)),
"Monitored",
Style::default().fg(theme::FG2),
)
} else {
(
),
MonitorState::Unmonitored => (
Span::styled("", Style::default().fg(theme::GRAY)),
"Unmonitored",
Style::default().fg(theme::GRAY),
)
),
MonitorState::Excluded => (
Span::styled("", Style::default().fg(theme::RED)),
"Excluded",
Style::default().fg(theme::RED),
),
MonitorState::Unspecified => (
Span::styled("? ", Style::default().fg(theme::GRAY)),
"Unknown",
Style::default().fg(theme::GRAY),
),
};
let lines = vec![