453 lines
14 KiB
Rust
453 lines
14 KiB
Rust
use std::sync::atomic::Ordering;
|
|
|
|
use ratatui::Frame;
|
|
use ratatui::layout::{Constraint, Layout, Rect};
|
|
use ratatui::widgets::{Cell, ListItem, Row};
|
|
|
|
use crate::models::Route;
|
|
use crate::models::radarr_models::AddMovieSearchResult;
|
|
use crate::models::servarr_data::radarr::modals::AddMovieModal;
|
|
use crate::models::servarr_data::radarr::radarr_data::{ADD_MOVIE_BLOCKS, ActiveRadarrBlock};
|
|
use crate::ui::radarr_ui::collections::CollectionsUi;
|
|
use crate::ui::styles::ManagarrStyle;
|
|
use crate::ui::utils::{
|
|
get_width_from_percentage, layout_block, layout_paragraph_borderless, title_block_centered,
|
|
};
|
|
use crate::ui::widgets::button::Button;
|
|
use crate::ui::widgets::input_box::InputBox;
|
|
use crate::ui::widgets::managarr_table::ManagarrTable;
|
|
use crate::ui::widgets::message::Message;
|
|
use crate::ui::widgets::popup::{Popup, Size};
|
|
use crate::ui::widgets::selectable_list::SelectableList;
|
|
use crate::ui::{DrawUi, draw_popup};
|
|
use crate::utils::convert_runtime;
|
|
use crate::{App, render_selectable_input_box};
|
|
|
|
#[cfg(test)]
|
|
#[path = "add_movie_ui_tests.rs"]
|
|
mod add_movie_ui_tests;
|
|
|
|
pub(super) struct AddMovieUi;
|
|
|
|
impl DrawUi for AddMovieUi {
|
|
fn accepts(route: Route) -> bool {
|
|
let Route::Radarr(active_radarr_block, _) = route else {
|
|
return false;
|
|
};
|
|
ADD_MOVIE_BLOCKS.contains(&active_radarr_block)
|
|
}
|
|
|
|
fn draw(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
|
if let Route::Radarr(active_radarr_block, context_option) = app.get_current_route() {
|
|
if context_option.is_some() {
|
|
CollectionsUi::draw(f, app, area);
|
|
draw_popup(f, app, draw_confirmation_popup, Size::Medium);
|
|
} else {
|
|
draw_popup(f, app, draw_add_movie_search, Size::Large);
|
|
|
|
match active_radarr_block {
|
|
ActiveRadarrBlock::AddMoviePrompt
|
|
| ActiveRadarrBlock::AddMovieSelectMonitor
|
|
| ActiveRadarrBlock::AddMovieSelectMinimumAvailability
|
|
| ActiveRadarrBlock::AddMovieSelectQualityProfile
|
|
| ActiveRadarrBlock::AddMovieSelectRootFolder
|
|
| ActiveRadarrBlock::AddMovieTagsInput => {
|
|
draw_popup(f, app, draw_confirmation_popup, Size::Medium);
|
|
}
|
|
ActiveRadarrBlock::AddMovieAlreadyInLibrary => {
|
|
f.render_widget(
|
|
Popup::new(Message::new("This film is already in your library")).size(Size::Message),
|
|
f.area(),
|
|
);
|
|
}
|
|
_ => (),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn draw_add_movie_search(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
|
let is_loading = app.is_loading || app.data.radarr_data.add_searched_movies.is_none();
|
|
let current_selection =
|
|
if let Some(add_searched_movies) = app.data.radarr_data.add_searched_movies.as_ref() {
|
|
add_searched_movies.current_selection().clone()
|
|
} else {
|
|
AddMovieSearchResult::default()
|
|
};
|
|
|
|
let [search_box_area, results_area] =
|
|
Layout::vertical([Constraint::Length(3), Constraint::Fill(0)])
|
|
.margin(1)
|
|
.areas(area);
|
|
let block_content = app
|
|
.data
|
|
.radarr_data
|
|
.add_movie_search
|
|
.as_ref()
|
|
.expect("add_movie_search must be populated")
|
|
.text
|
|
.clone();
|
|
let offset = app
|
|
.data
|
|
.radarr_data
|
|
.add_movie_search
|
|
.as_ref()
|
|
.expect("add_movie_search must be populated")
|
|
.offset
|
|
.load(Ordering::SeqCst);
|
|
let ui_scroll_tick_count = app.ui_scroll_tick_count;
|
|
let library_tmdb_ids: Vec<i64> = app
|
|
.data
|
|
.radarr_data
|
|
.movies
|
|
.items
|
|
.iter()
|
|
.map(|m| m.tmdb_id)
|
|
.collect();
|
|
|
|
let search_results_row_mapping = |movie: &AddMovieSearchResult| {
|
|
let (hours, minutes) = convert_runtime(movie.runtime);
|
|
let imdb_rating = movie
|
|
.ratings
|
|
.imdb
|
|
.clone()
|
|
.unwrap_or_default()
|
|
.value
|
|
.as_f64()
|
|
.unwrap_or_default();
|
|
let rotten_tomatoes_rating = movie
|
|
.ratings
|
|
.rotten_tomatoes
|
|
.clone()
|
|
.unwrap_or_default()
|
|
.value
|
|
.as_u64()
|
|
.unwrap_or_default();
|
|
let imdb_rating = if imdb_rating == 0.0 {
|
|
String::new()
|
|
} else {
|
|
format!("{imdb_rating:.1}")
|
|
};
|
|
let rotten_tomatoes_rating = if rotten_tomatoes_rating == 0 {
|
|
String::new()
|
|
} else {
|
|
format!("{rotten_tomatoes_rating}%")
|
|
};
|
|
let in_library = if library_tmdb_ids.contains(&movie.tmdb_id) {
|
|
"✔"
|
|
} else {
|
|
""
|
|
};
|
|
|
|
movie.title.scroll_left_or_reset(
|
|
get_width_from_percentage(area, 27),
|
|
*movie == current_selection,
|
|
ui_scroll_tick_count == 0,
|
|
);
|
|
|
|
Row::new(vec![
|
|
Cell::from(in_library),
|
|
Cell::from(movie.title.to_string()),
|
|
Cell::from(movie.year.to_string()),
|
|
Cell::from(format!("{hours}h {minutes}m")),
|
|
Cell::from(imdb_rating),
|
|
Cell::from(rotten_tomatoes_rating),
|
|
Cell::from(movie.genres.join(", ")),
|
|
])
|
|
.primary()
|
|
};
|
|
|
|
if let Route::Radarr(active_radarr_block, _) = app.get_current_route() {
|
|
match active_radarr_block {
|
|
ActiveRadarrBlock::AddMovieSearchInput => {
|
|
let search_box = InputBox::new(&block_content)
|
|
.offset(offset)
|
|
.block(title_block_centered("Add Movie"));
|
|
|
|
search_box.show_cursor(f, search_box_area);
|
|
f.render_widget(layout_block().default(), results_area);
|
|
f.render_widget(search_box, search_box_area);
|
|
}
|
|
ActiveRadarrBlock::AddMovieEmptySearchResults => {
|
|
let error_message = Message::new("No movies found matching your query!");
|
|
let error_message_popup = Popup::new(error_message).size(Size::Message);
|
|
|
|
f.render_widget(layout_block().default(), results_area);
|
|
f.render_widget(error_message_popup, f.area());
|
|
}
|
|
ActiveRadarrBlock::AddMovieSearchResults
|
|
| ActiveRadarrBlock::AddMoviePrompt
|
|
| ActiveRadarrBlock::AddMovieSelectMonitor
|
|
| ActiveRadarrBlock::AddMovieSelectMinimumAvailability
|
|
| ActiveRadarrBlock::AddMovieSelectQualityProfile
|
|
| ActiveRadarrBlock::AddMovieSelectRootFolder
|
|
| ActiveRadarrBlock::AddMovieAlreadyInLibrary
|
|
| ActiveRadarrBlock::AddMovieTagsInput => {
|
|
let search_results_table = ManagarrTable::new(
|
|
app,
|
|
|app| app.data.radarr_data.add_searched_movies.as_mut(),
|
|
search_results_row_mapping,
|
|
)
|
|
.loading(is_loading)
|
|
.block(layout_block().default())
|
|
.headers([
|
|
"✔",
|
|
"Title",
|
|
"Year",
|
|
"Runtime",
|
|
"IMDB",
|
|
"Rotten Tomatoes",
|
|
"Genres",
|
|
])
|
|
.constraints([
|
|
Constraint::Percentage(2),
|
|
Constraint::Percentage(27),
|
|
Constraint::Percentage(8),
|
|
Constraint::Percentage(10),
|
|
Constraint::Percentage(8),
|
|
Constraint::Percentage(14),
|
|
Constraint::Percentage(28),
|
|
]);
|
|
|
|
f.render_widget(search_results_table, results_area);
|
|
}
|
|
_ => (),
|
|
}
|
|
}
|
|
|
|
f.render_widget(
|
|
InputBox::new(&block_content)
|
|
.offset(offset)
|
|
.block(title_block_centered("Add Movie")),
|
|
search_box_area,
|
|
);
|
|
}
|
|
|
|
fn draw_confirmation_popup(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
|
if let Route::Radarr(active_radarr_block, _) = app.get_current_route() {
|
|
draw_confirmation_prompt(f, app, area);
|
|
|
|
match active_radarr_block {
|
|
ActiveRadarrBlock::AddMovieSelectMonitor => {
|
|
draw_add_movie_select_monitor_popup(f, app);
|
|
}
|
|
ActiveRadarrBlock::AddMovieSelectMinimumAvailability => {
|
|
draw_add_movie_select_minimum_availability_popup(f, app);
|
|
}
|
|
ActiveRadarrBlock::AddMovieSelectQualityProfile => {
|
|
draw_add_movie_select_quality_profile_popup(f, app);
|
|
}
|
|
ActiveRadarrBlock::AddMovieSelectRootFolder => {
|
|
draw_add_movie_select_root_folder_popup(f, app);
|
|
}
|
|
_ => (),
|
|
}
|
|
}
|
|
}
|
|
|
|
fn draw_confirmation_prompt(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
|
let (movie_title, movie_overview) = if let Route::Radarr(_, Some(_)) = app.get_current_route() {
|
|
(
|
|
&app
|
|
.data
|
|
.radarr_data
|
|
.collection_movies
|
|
.current_selection()
|
|
.title
|
|
.text,
|
|
app
|
|
.data
|
|
.radarr_data
|
|
.collection_movies
|
|
.current_selection()
|
|
.overview
|
|
.clone(),
|
|
)
|
|
} else {
|
|
(
|
|
&app
|
|
.data
|
|
.radarr_data
|
|
.add_searched_movies
|
|
.as_ref()
|
|
.expect("add_searched_movies must be populated")
|
|
.current_selection()
|
|
.title
|
|
.text,
|
|
app
|
|
.data
|
|
.radarr_data
|
|
.add_searched_movies
|
|
.as_ref()
|
|
.expect("add_searched_movies must be populated")
|
|
.current_selection()
|
|
.overview
|
|
.clone(),
|
|
)
|
|
};
|
|
let title = format!("Add Movie - {movie_title}");
|
|
let prompt = movie_overview;
|
|
let yes_no_value = app.data.radarr_data.prompt_confirm;
|
|
let selected_block = app.data.radarr_data.selected_block.get_active_block();
|
|
let highlight_yes_no = selected_block == ActiveRadarrBlock::AddMovieConfirmPrompt;
|
|
let AddMovieModal {
|
|
monitor_list,
|
|
minimum_availability_list,
|
|
quality_profile_list,
|
|
root_folder_list,
|
|
tags,
|
|
..
|
|
} = app
|
|
.data
|
|
.radarr_data
|
|
.add_movie_modal
|
|
.as_ref()
|
|
.expect("add_movie_modal must exist in this context");
|
|
|
|
let selected_monitor = monitor_list.current_selection();
|
|
let selected_minimum_availability = minimum_availability_list.current_selection();
|
|
let selected_quality_profile = quality_profile_list.current_selection();
|
|
let selected_root_folder = root_folder_list.current_selection();
|
|
|
|
f.render_widget(title_block_centered(&title), area);
|
|
|
|
let [
|
|
paragraph_area,
|
|
root_folder_area,
|
|
monitor_area,
|
|
min_availability_area,
|
|
quality_profile_area,
|
|
tags_area,
|
|
_,
|
|
buttons_area,
|
|
] = Layout::vertical([
|
|
Constraint::Length(6),
|
|
Constraint::Length(3),
|
|
Constraint::Length(3),
|
|
Constraint::Length(3),
|
|
Constraint::Length(3),
|
|
Constraint::Length(3),
|
|
Constraint::Fill(1),
|
|
Constraint::Length(3),
|
|
])
|
|
.margin(1)
|
|
.areas(area);
|
|
|
|
let prompt_paragraph = layout_paragraph_borderless(&prompt);
|
|
f.render_widget(prompt_paragraph, paragraph_area);
|
|
|
|
let [add_area, cancel_area] =
|
|
Layout::horizontal([Constraint::Percentage(50), Constraint::Percentage(50)])
|
|
.areas(buttons_area);
|
|
|
|
let root_folder_drop_down_button = Button::new()
|
|
.title(&selected_root_folder.path)
|
|
.label("Root Folder")
|
|
.icon("▼")
|
|
.selected(selected_block == ActiveRadarrBlock::AddMovieSelectRootFolder);
|
|
let monitor_drop_down_button = Button::new()
|
|
.title(selected_monitor.to_display_str())
|
|
.label("Monitor")
|
|
.icon("▼")
|
|
.selected(selected_block == ActiveRadarrBlock::AddMovieSelectMonitor);
|
|
let min_availability_drop_down_button = Button::new()
|
|
.title(selected_minimum_availability.to_display_str())
|
|
.label("Minimum Availability")
|
|
.icon("▼")
|
|
.selected(selected_block == ActiveRadarrBlock::AddMovieSelectMinimumAvailability);
|
|
let quality_profile_drop_down_button = Button::new()
|
|
.title(selected_quality_profile)
|
|
.label("Quality Profile")
|
|
.icon("▼")
|
|
.selected(selected_block == ActiveRadarrBlock::AddMovieSelectQualityProfile);
|
|
|
|
f.render_widget(root_folder_drop_down_button, root_folder_area);
|
|
f.render_widget(monitor_drop_down_button, monitor_area);
|
|
f.render_widget(min_availability_drop_down_button, min_availability_area);
|
|
f.render_widget(quality_profile_drop_down_button, quality_profile_area);
|
|
|
|
if let Route::Radarr(active_radarr_block, _) = app.get_current_route() {
|
|
let tags_input_box = InputBox::new(&tags.text)
|
|
.offset(tags.offset.load(Ordering::SeqCst))
|
|
.label("Tags")
|
|
.highlighted(selected_block == ActiveRadarrBlock::AddMovieTagsInput)
|
|
.selected(active_radarr_block == ActiveRadarrBlock::AddMovieTagsInput);
|
|
render_selectable_input_box!(tags_input_box, f, tags_area);
|
|
}
|
|
|
|
let add_button = Button::new()
|
|
.title("Add")
|
|
.selected(yes_no_value && highlight_yes_no);
|
|
let cancel_button = Button::new()
|
|
.title("Cancel")
|
|
.selected(!yes_no_value && highlight_yes_no);
|
|
|
|
f.render_widget(add_button, add_area);
|
|
f.render_widget(cancel_button, cancel_area);
|
|
}
|
|
|
|
fn draw_add_movie_select_monitor_popup(f: &mut Frame<'_>, app: &mut App<'_>) {
|
|
let monitor_list = SelectableList::new(
|
|
&mut app
|
|
.data
|
|
.radarr_data
|
|
.add_movie_modal
|
|
.as_mut()
|
|
.expect("add_movie_modal must exist in this context")
|
|
.monitor_list,
|
|
|monitor| ListItem::new(monitor.to_display_str().to_owned()),
|
|
);
|
|
let popup = Popup::new(monitor_list).size(Size::Dropdown);
|
|
|
|
f.render_widget(popup, f.area());
|
|
}
|
|
|
|
fn draw_add_movie_select_minimum_availability_popup(f: &mut Frame<'_>, app: &mut App<'_>) {
|
|
let minimum_availability_list = SelectableList::new(
|
|
&mut app
|
|
.data
|
|
.radarr_data
|
|
.add_movie_modal
|
|
.as_mut()
|
|
.expect("add_movie_modal must exist in this context")
|
|
.minimum_availability_list,
|
|
|minimum_availability| ListItem::new(minimum_availability.to_display_str().to_owned()),
|
|
);
|
|
let popup = Popup::new(minimum_availability_list).size(Size::Dropdown);
|
|
|
|
f.render_widget(popup, f.area());
|
|
}
|
|
|
|
fn draw_add_movie_select_quality_profile_popup(f: &mut Frame<'_>, app: &mut App<'_>) {
|
|
let quality_profile_list = SelectableList::new(
|
|
&mut app
|
|
.data
|
|
.radarr_data
|
|
.add_movie_modal
|
|
.as_mut()
|
|
.expect("add_movie_modal must exist in this context")
|
|
.quality_profile_list,
|
|
|quality_profile| ListItem::new(quality_profile.clone()),
|
|
);
|
|
let popup = Popup::new(quality_profile_list).size(Size::Dropdown);
|
|
|
|
f.render_widget(popup, f.area());
|
|
}
|
|
|
|
fn draw_add_movie_select_root_folder_popup(f: &mut Frame<'_>, app: &mut App<'_>) {
|
|
let root_folder_list = SelectableList::new(
|
|
&mut app
|
|
.data
|
|
.radarr_data
|
|
.add_movie_modal
|
|
.as_mut()
|
|
.expect("add_movie_modal must exist in this context")
|
|
.root_folder_list,
|
|
|root_folder| ListItem::new(root_folder.path.to_owned()),
|
|
);
|
|
let popup = Popup::new(root_folder_list).size(Size::Dropdown);
|
|
|
|
f.render_widget(popup, f.area());
|
|
}
|