Full popup description functionality
This commit is contained in:
@@ -12,6 +12,7 @@ clap = { version = "4.0.30", features = ["help", "usage", "error-context", "deri
|
||||
confy = { version = "0.5.1", default_features = false, features = ["yaml_conf"] }
|
||||
crossterm = "0.25.0"
|
||||
derivative = "2.2.0"
|
||||
indoc = "1.0.8"
|
||||
log = "0.4.17"
|
||||
log4rs = { version = "1.2.0", features = ["file_appender"] }
|
||||
reqwest = { version = "0.11.13", features = ["json"] }
|
||||
|
||||
+22
-9
@@ -1,7 +1,10 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use log::{debug, error};
|
||||
use reqwest::Client;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::sync::mpsc::Sender;
|
||||
use tokio::time::Instant;
|
||||
|
||||
use crate::app::radarr::{ActiveRadarrBlock, RadarrData};
|
||||
use crate::network::radarr_network::RadarrEvent;
|
||||
@@ -11,7 +14,7 @@ pub(crate) mod key_binding;
|
||||
pub mod models;
|
||||
pub mod radarr;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub enum Route {
|
||||
Radarr(ActiveRadarrBlock),
|
||||
}
|
||||
@@ -31,6 +34,8 @@ pub struct App {
|
||||
pub title: &'static str,
|
||||
pub tick_until_poll: u64,
|
||||
pub tick_count: u64,
|
||||
pub last_tick: Instant,
|
||||
pub network_tick_frequency: Duration,
|
||||
pub is_routing: bool,
|
||||
pub is_loading: bool,
|
||||
pub config: AppConfig,
|
||||
@@ -38,17 +43,15 @@ pub struct App {
|
||||
}
|
||||
|
||||
impl App {
|
||||
pub fn new(network_tx: Sender<NetworkEvent>, tick_until_poll: u64, config: AppConfig) -> Self {
|
||||
pub fn new(network_tx: Sender<NetworkEvent>, config: AppConfig) -> Self {
|
||||
App {
|
||||
network_tx: Some(network_tx),
|
||||
tick_until_poll,
|
||||
config,
|
||||
..App::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn dispatch(&mut self, action: NetworkEvent) {
|
||||
self.is_loading = true;
|
||||
if let Some(network_tx) = &self.network_tx {
|
||||
if let Err(e) = network_tx.send(action).await {
|
||||
self.is_loading = false;
|
||||
@@ -74,12 +77,20 @@ impl App {
|
||||
|
||||
if is_first_render {
|
||||
self.dispatch(RadarrEvent::GetQualityProfiles.into()).await;
|
||||
self.dispatch(RadarrEvent::GetOverview.into()).await;
|
||||
self.dispatch(RadarrEvent::GetStatus.into()).await;
|
||||
self.dispatch_by_radarr_block(active_block.clone()).await;
|
||||
}
|
||||
|
||||
self.dispatch(RadarrEvent::GetOverview.into()).await;
|
||||
self.dispatch(RadarrEvent::GetStatus.into()).await;
|
||||
|
||||
self.dispatch_by_radarr_block(active_block).await;
|
||||
if self.is_routing
|
||||
|| self
|
||||
.network_tick_frequency
|
||||
.checked_sub(self.last_tick.elapsed())
|
||||
.unwrap_or_else(|| Duration::from_secs(0))
|
||||
.is_zero()
|
||||
{
|
||||
self.dispatch_by_radarr_block(active_block).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,8 +124,10 @@ impl Default for App {
|
||||
network_tx: None,
|
||||
client: Client::new(),
|
||||
title: "Managarr",
|
||||
tick_until_poll: 0,
|
||||
tick_until_poll: 20,
|
||||
tick_count: 0,
|
||||
network_tick_frequency: Duration::from_secs(5),
|
||||
last_tick: Instant::now(),
|
||||
is_loading: false,
|
||||
is_routing: false,
|
||||
config: AppConfig::default(),
|
||||
|
||||
+7
-3
@@ -18,7 +18,7 @@ pub struct RadarrData {
|
||||
pub movie_details: ScrollableText,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub enum ActiveRadarrBlock {
|
||||
AddMovie,
|
||||
Calendar,
|
||||
@@ -35,15 +35,19 @@ pub enum ActiveRadarrBlock {
|
||||
|
||||
impl App {
|
||||
pub(super) async fn dispatch_by_radarr_block(&mut self, active_radarr_block: ActiveRadarrBlock) {
|
||||
self.reset_tick_count();
|
||||
match active_radarr_block {
|
||||
ActiveRadarrBlock::Downloads => self.dispatch(RadarrEvent::GetDownloads.into()).await,
|
||||
ActiveRadarrBlock::Movies => {
|
||||
self.dispatch(RadarrEvent::GetMovies.into()).await;
|
||||
self.dispatch(RadarrEvent::GetDownloads.into()).await;
|
||||
}
|
||||
ActiveRadarrBlock::MovieDetails => self.dispatch(RadarrEvent::GetMovieDetails.into()).await,
|
||||
ActiveRadarrBlock::MovieDetails => {
|
||||
self.is_loading = true;
|
||||
self.dispatch(RadarrEvent::GetMovieDetails.into()).await
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
self.reset_tick_count();
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ use crate::handlers::radarr_handler::handle_radarr_key_events;
|
||||
mod radarr_handler;
|
||||
|
||||
pub async fn handle_key_events(key: Key, app: &mut App) {
|
||||
match *app.get_current_route() {
|
||||
match app.get_current_route().clone() {
|
||||
Route::Radarr(active_radarr_block) => {
|
||||
handle_radarr_key_events(key, app, active_radarr_block).await
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
|
||||
use crate::app::models::Scrollable;
|
||||
use crate::app::models::{Scrollable, ScrollableText};
|
||||
use crate::app::radarr::ActiveRadarrBlock;
|
||||
use crate::{App, Key};
|
||||
|
||||
@@ -42,7 +42,10 @@ async fn handle_submit(app: &mut App, active_radarr_block: ActiveRadarrBlock) {
|
||||
|
||||
async fn handle_esc(app: &mut App, active_radarr_block: ActiveRadarrBlock) {
|
||||
match active_radarr_block {
|
||||
ActiveRadarrBlock::MovieDetails => app.pop_navigation_stack(),
|
||||
ActiveRadarrBlock::MovieDetails => {
|
||||
app.pop_navigation_stack();
|
||||
app.data.radarr_data.movie_details = ScrollableText::default();
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
+6
-5
@@ -39,7 +39,7 @@ async fn main() -> Result<()> {
|
||||
let config = confy::load("managarr", "config")?;
|
||||
let (sync_network_tx, sync_network_rx) = mpsc::channel(500);
|
||||
|
||||
let app = Arc::new(Mutex::new(App::new(sync_network_tx, 5000 / 250, config)));
|
||||
let app = Arc::new(Mutex::new(App::new(sync_network_tx, config)));
|
||||
|
||||
let app_nw = Arc::clone(&app);
|
||||
|
||||
@@ -55,10 +55,6 @@ async fn start_networking(mut network_rx: Receiver<NetworkEvent>, app: &Arc<Mute
|
||||
let network = Network::new(reqwest::Client::new(), app);
|
||||
|
||||
while let Some(network_event) = network_rx.recv().await {
|
||||
debug!(
|
||||
"**************************************MAIN Received network event: {:?}",
|
||||
network_event
|
||||
);
|
||||
network.handle_network_event(network_event).await;
|
||||
}
|
||||
}
|
||||
@@ -78,6 +74,11 @@ async fn start_ui(app: &Arc<Mutex<App>>) -> Result<()> {
|
||||
|
||||
loop {
|
||||
let mut app = app.lock().await;
|
||||
|
||||
if is_first_render {
|
||||
app.is_loading = true;
|
||||
}
|
||||
|
||||
terminal.draw(|f| ui(f, &mut app))?;
|
||||
|
||||
match input_events.next()? {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use derivative::Derivative;
|
||||
use indoc::{formatdoc, indoc};
|
||||
use log::{debug, error};
|
||||
use reqwest::RequestBuilder;
|
||||
use serde::de::DeserializeOwned;
|
||||
@@ -148,7 +149,6 @@ impl<'a> Network<'a> {
|
||||
RadarrEvent::GetStatus => self.get_status(RadarrEvent::GetStatus.resource()).await,
|
||||
RadarrEvent::GetMovies => self.get_movies(RadarrEvent::GetMovies.resource()).await,
|
||||
RadarrEvent::GetMovieDetails => {
|
||||
debug!("TEST received GetMovieDetails event");
|
||||
self
|
||||
.get_movie_details(RadarrEvent::GetMovieDetails.resource())
|
||||
.await
|
||||
@@ -164,8 +164,6 @@ impl<'a> Network<'a> {
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
let mut app = self.app.lock().await;
|
||||
}
|
||||
|
||||
async fn get_healthcheck(&self, resource: &str) {
|
||||
@@ -207,7 +205,6 @@ impl<'a> Network<'a> {
|
||||
}
|
||||
|
||||
async fn get_movie_details(&self, resource: &str) {
|
||||
debug!("TEST handling get_movie_details");
|
||||
let movie_id = self
|
||||
.app
|
||||
.lock()
|
||||
@@ -223,7 +220,6 @@ impl<'a> Network<'a> {
|
||||
let mut url = resource.to_owned();
|
||||
url.push('/');
|
||||
url.push_str(movie_id.to_string().as_str());
|
||||
debug!("TEST sending request{}", url.as_str());
|
||||
self
|
||||
.handle_get_request::<Movie>(url.as_str(), |movie_response, mut app| {
|
||||
let Movie {
|
||||
@@ -251,39 +247,51 @@ impl<'a> Network<'a> {
|
||||
.unwrap()
|
||||
.to_owned();
|
||||
let imdb_rating = if let Some(rating) = ratings.imdb {
|
||||
rating.value.as_f64().unwrap()
|
||||
if let Some(value) = rating.value.as_f64() {
|
||||
format!("{:.1}", value)
|
||||
} else {
|
||||
"".to_owned()
|
||||
}
|
||||
} else {
|
||||
0f64
|
||||
"".to_owned()
|
||||
};
|
||||
|
||||
let tmdb_rating = if let Some(rating) = ratings.tmdb {
|
||||
rating.value.as_u64().unwrap()
|
||||
if let Some(value) = rating.value.as_f64() {
|
||||
format!("{}%", value * 10f64)
|
||||
} else {
|
||||
"".to_owned()
|
||||
}
|
||||
} else {
|
||||
0u64
|
||||
"".to_owned()
|
||||
};
|
||||
|
||||
let rotten_tomatoes_rating = if let Some(rating) = ratings.rotten_tomatoes {
|
||||
rating.value.as_u64().unwrap()
|
||||
if let Some(value) = rating.value.as_u64() {
|
||||
format!("{}%", value)
|
||||
} else {
|
||||
"".to_owned()
|
||||
}
|
||||
} else {
|
||||
0u64
|
||||
"".to_owned()
|
||||
};
|
||||
|
||||
let status = get_movie_status(has_file, &app.data.radarr_data.downloads.items, id);
|
||||
|
||||
app.data.radarr_data.movie_details = ScrollableText::with_string(format!(
|
||||
"Title: {}\n
|
||||
Year: {}\n
|
||||
Runtime: {}h {}m\n
|
||||
Status: {}\n
|
||||
Description: {}\n
|
||||
TMDB: {}%\n
|
||||
IMDB: {:.1}\n
|
||||
Rotten Tomatoes: {}%\n
|
||||
Quality Profile: {}\n
|
||||
Size: {:.2} GB\n
|
||||
Path: {}\n
|
||||
Studio: {}\n
|
||||
Genres: {}",
|
||||
app.data.radarr_data.movie_details = ScrollableText::with_string(formatdoc!(
|
||||
"Title: {}
|
||||
Year: {}
|
||||
Runtime: {}h {}m
|
||||
Status: {}
|
||||
Description: {}
|
||||
TMDB: {}
|
||||
IMDB: {}
|
||||
Rotten Tomatoes: {}
|
||||
Quality Profile: {}
|
||||
Size: {:.2} GB
|
||||
Path: {}
|
||||
Studio: {}
|
||||
Genres: {}",
|
||||
title,
|
||||
year,
|
||||
hours,
|
||||
|
||||
@@ -10,20 +10,20 @@ pub async fn parse_response<T: DeserializeOwned>(response: Response) -> Result<T
|
||||
|
||||
pub fn get_movie_status(
|
||||
has_file: bool,
|
||||
downloads_vec: &Vec<DownloadRecord>,
|
||||
downloads_vec: &[DownloadRecord],
|
||||
movie_id: Number,
|
||||
) -> String {
|
||||
if !has_file {
|
||||
if let Some(download) = downloads_vec
|
||||
.iter()
|
||||
.find(|&download| download.movie_id == movie_id)
|
||||
.find(|&download| download.movie_id.as_u64().unwrap() == movie_id.as_u64().unwrap())
|
||||
{
|
||||
if download.status == "downloading" {
|
||||
return "Downloading".to_owned();
|
||||
}
|
||||
|
||||
return "Missing".to_owned();
|
||||
}
|
||||
|
||||
return "Missing".to_owned();
|
||||
}
|
||||
|
||||
"Downloaded".to_owned()
|
||||
|
||||
+40
-6
@@ -11,7 +11,7 @@ use crate::logos::{
|
||||
};
|
||||
use crate::ui::utils::{
|
||||
centered_rect, horizontal_chunks, horizontal_chunks_with_margin, style_secondary,
|
||||
vertical_chunks, vertical_chunks_with_margin,
|
||||
style_system_function, vertical_chunks, vertical_chunks_with_margin,
|
||||
};
|
||||
|
||||
mod radarr_ui;
|
||||
@@ -27,10 +27,10 @@ pub fn ui<B: Backend>(f: &mut Frame<B>, app: &mut App) {
|
||||
);
|
||||
|
||||
draw_context_row(f, app, main_chunks[0]);
|
||||
match *app.get_current_route() {
|
||||
match app.get_current_route().clone() {
|
||||
Route::Radarr(active_radarr_block) => match active_radarr_block {
|
||||
ActiveRadarrBlock::Movies => radarr_ui::draw_radarr_ui(f, app, main_chunks[1]),
|
||||
ActiveRadarrBlock::MovieDetails => draw_popup_over(
|
||||
ActiveRadarrBlock::MovieDetails => draw_small_popup_over(
|
||||
f,
|
||||
app,
|
||||
main_chunks[1],
|
||||
@@ -48,14 +48,46 @@ pub fn draw_popup_over<B: Backend>(
|
||||
area: Rect,
|
||||
background_fn: fn(&mut Frame<'_, B>, &mut App, Rect),
|
||||
popup_fn: fn(&mut Frame<'_, B>, &App, Rect),
|
||||
percent_x: u16,
|
||||
percent_y: u16,
|
||||
) {
|
||||
background_fn(f, app, area);
|
||||
|
||||
let popup_area = centered_rect(75, 75, f.size());
|
||||
let popup_area = centered_rect(percent_x, percent_y, f.size());
|
||||
f.render_widget(Clear, popup_area);
|
||||
popup_fn(f, app, popup_area);
|
||||
}
|
||||
|
||||
pub fn draw_small_popup_over<B: Backend>(
|
||||
f: &mut Frame<'_, B>,
|
||||
app: &mut App,
|
||||
area: Rect,
|
||||
background_fn: fn(&mut Frame<'_, B>, &mut App, Rect),
|
||||
popup_fn: fn(&mut Frame<'_, B>, &App, Rect),
|
||||
) {
|
||||
draw_popup_over(f, app, area, background_fn, popup_fn, 40, 40);
|
||||
}
|
||||
|
||||
pub fn draw_medium_popup_over<B: Backend>(
|
||||
f: &mut Frame<'_, B>,
|
||||
app: &mut App,
|
||||
area: Rect,
|
||||
background_fn: fn(&mut Frame<'_, B>, &mut App, Rect),
|
||||
popup_fn: fn(&mut Frame<'_, B>, &App, Rect),
|
||||
) {
|
||||
draw_popup_over(f, app, area, background_fn, popup_fn, 60, 60);
|
||||
}
|
||||
|
||||
pub fn draw_large_popup_over<B: Backend>(
|
||||
f: &mut Frame<'_, B>,
|
||||
app: &mut App,
|
||||
area: Rect,
|
||||
background_fn: fn(&mut Frame<'_, B>, &mut App, Rect),
|
||||
popup_fn: fn(&mut Frame<'_, B>, &App, Rect),
|
||||
) {
|
||||
draw_popup_over(f, app, area, background_fn, popup_fn, 75, 75);
|
||||
}
|
||||
|
||||
fn draw_context_row<B: Backend>(f: &mut Frame<'_, B>, app: &App, area: Rect) {
|
||||
let chunks = horizontal_chunks(
|
||||
vec![
|
||||
@@ -80,9 +112,11 @@ pub fn loading<B: Backend>(f: &mut Frame<'_, B>, block: Block<'_>, area: Rect, i
|
||||
if is_loading {
|
||||
let text = "\n\n Loading ...\n\n".to_owned();
|
||||
let mut text = Text::from(text);
|
||||
text.patch_style(style_secondary());
|
||||
text.patch_style(style_system_function());
|
||||
|
||||
let paragraph = Paragraph::new(text).style(style_secondary()).block(block);
|
||||
let paragraph = Paragraph::new(text)
|
||||
.style(style_system_function())
|
||||
.block(block);
|
||||
f.render_widget(paragraph, area);
|
||||
} else {
|
||||
f.render_widget(block, area)
|
||||
|
||||
+23
-3
@@ -1,6 +1,7 @@
|
||||
use std::ops::Sub;
|
||||
|
||||
use chrono::{Duration, Utc};
|
||||
use log::debug;
|
||||
use tui::backend::Backend;
|
||||
use tui::layout::{Alignment, Constraint, Rect};
|
||||
use tui::style::{Color, Modifier, Style};
|
||||
@@ -81,11 +82,21 @@ pub(super) fn draw_radarr_ui<B: Backend>(f: &mut Frame<'_, B>, app: &mut App, ar
|
||||
|
||||
pub(super) fn draw_movie_details<B: Backend>(f: &mut Frame<'_, B>, app: &App, area: Rect) {
|
||||
let block = title_block("Movie Details");
|
||||
let movie_details = &app.data.radarr_data.movie_details.get_text();
|
||||
let movie_details = app.data.radarr_data.movie_details.get_text();
|
||||
|
||||
if !movie_details.is_empty() {
|
||||
let mut text = Text::from(movie_details.clone());
|
||||
text.patch_style(style_primary());
|
||||
let download_status = app
|
||||
.data
|
||||
.radarr_data
|
||||
.movie_details
|
||||
.items
|
||||
.iter()
|
||||
.find(|&line| line.starts_with("Status: "))
|
||||
.unwrap()
|
||||
.split(": ")
|
||||
.collect::<Vec<&str>>()[1];
|
||||
let mut text = Text::from(movie_details);
|
||||
text.patch_style(determine_style_from_download_status(download_status));
|
||||
|
||||
let paragraph = Paragraph::new(text)
|
||||
.block(block)
|
||||
@@ -197,3 +208,12 @@ fn determine_row_style(app: &App, movie: &Movie) -> Style {
|
||||
|
||||
style_primary()
|
||||
}
|
||||
|
||||
fn determine_style_from_download_status(download_status: &str) -> Style {
|
||||
match download_status {
|
||||
"Downloaded" => style_primary(),
|
||||
"Downloading" => style_secondary(),
|
||||
"Missing" => style_tertiary(),
|
||||
_ => style_primary(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,6 +65,10 @@ pub fn style_default() -> Style {
|
||||
Style::default().fg(Color::White)
|
||||
}
|
||||
|
||||
pub fn style_system_function() -> Style {
|
||||
Style::default().fg(Color::Yellow)
|
||||
}
|
||||
|
||||
pub fn style_primary() -> Style {
|
||||
Style::default().fg(Color::Green)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user