From 4fb83c114a595d93a00416711be5825daa03965f Mon Sep 17 00:00:00 2001 From: Dark-Alex-17 Date: Tue, 8 Aug 2023 10:50:06 -0600 Subject: [PATCH] Cleaned up the block selection logic to use the new BlockSelectionState struct --- Cargo.toml | 2 +- src/app/mod.rs | 30 +- src/app/radarr.rs | 569 +++++------------- src/handlers/mod.rs | 10 +- .../radarr_handlers/add_movie_handler.rs | 136 +++-- .../collection_details_handler.rs | 37 +- .../radarr_handlers/delete_movie_handler.rs | 60 +- .../edit_collection_handler.rs | 162 ++--- .../radarr_handlers/edit_movie_handler.rs | 132 ++-- src/handlers/radarr_handlers/mod.rs | 56 +- .../radarr_handlers/movie_details_handler.rs | 19 +- src/main.rs | 4 +- src/models/mod.rs | 166 ++++- src/network/mod.rs | 14 +- src/network/radarr_network.rs | 103 ++-- src/ui/mod.rs | 58 +- src/ui/radarr_ui/add_movie_ui.rs | 32 +- src/ui/radarr_ui/collection_details_ui.rs | 6 +- src/ui/radarr_ui/delete_movie_ui.rs | 10 +- src/ui/radarr_ui/edit_collection_ui.rs | 18 +- src/ui/radarr_ui/edit_movie_ui.rs | 18 +- src/ui/radarr_ui/mod.rs | 46 +- src/ui/radarr_ui/movie_details_ui.rs | 34 +- 23 files changed, 840 insertions(+), 882 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5d669a3..88da2d0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "managarr" -version = "0.0.16" +version = "0.0.17" authors = ["Alex Clarke "] description = "A TUI for managing *arr servers" keywords = ["managarr", "tui-rs", "dashboard", "servarr"] diff --git a/src/app/mod.rs b/src/app/mod.rs index 1d376cc..2386795 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -14,7 +14,7 @@ pub(crate) mod radarr; const DEFAULT_ROUTE: Route = Route::Radarr(ActiveRadarrBlock::Movies, None); -pub struct App { +pub struct App<'a> { navigation_stack: Vec, network_tx: Option>, pub server_tabs: TabState, @@ -31,10 +31,10 @@ pub struct App { pub should_refresh: bool, pub should_ignore_quit_key: bool, pub config: AppConfig, - pub data: Data, + pub data: Data<'a>, } -impl App { +impl<'a> App<'a> { pub fn new(network_tx: Sender, config: AppConfig) -> Self { App { network_tx: Some(network_tx), @@ -106,12 +106,12 @@ impl App { self.push_navigation_stack(route); } - pub fn get_current_route(&self) -> &Route { + pub fn get_current_route(&'a self) -> &'a Route { self.navigation_stack.last().unwrap_or(&DEFAULT_ROUTE) } } -impl Default for App { +impl<'a> Default for App<'a> { fn default() -> Self { App { navigation_stack: vec![DEFAULT_ROUTE], @@ -120,15 +120,15 @@ impl Default for App { response: String::default(), server_tabs: TabState::new(vec![ TabRoute { - title: "Radarr".to_owned(), + title: "Radarr", route: ActiveRadarrBlock::Movies.into(), - help: "<↑↓> scroll | ←→ change tab | change servarr | quit ".to_owned(), + help: "<↑↓> scroll | ←→ change tab | change servarr | quit ", contextual_help: None, }, TabRoute { - title: "Sonarr".to_owned(), + title: "Sonarr", route: Route::Sonarr, - help: " change servarr | quit ".to_owned(), + help: " change servarr | quit ", contextual_help: None, }, ]), @@ -149,8 +149,8 @@ impl Default for App { } #[derive(Default)] -pub struct Data { - pub radarr_data: RadarrData, +pub struct Data<'a> { + pub radarr_data: RadarrData<'a>, } #[derive(Debug, Deserialize, Serialize, Default)] @@ -200,15 +200,15 @@ mod tests { app.server_tabs.tabs, vec![ TabRoute { - title: "Radarr".to_owned(), + title: "Radarr", route: ActiveRadarrBlock::Movies.into(), - help: "<↑↓> scroll | ←→ change tab | change servarr | quit ".to_owned(), + help: "<↑↓> scroll | ←→ change tab | change servarr | quit ", contextual_help: None, }, TabRoute { - title: "Sonarr".to_owned(), + title: "Sonarr", route: Route::Sonarr, - help: " change servarr | quit ".to_owned(), + help: " change servarr | quit ", contextual_help: None, } ] diff --git a/src/app/radarr.rs b/src/app/radarr.rs index 45c8b1a..2b4551c 100644 --- a/src/app/radarr.rs +++ b/src/app/radarr.rs @@ -8,11 +8,12 @@ use crate::models::radarr_models::{ MinimumAvailability, Monitor, Movie, MovieHistoryItem, Release, ReleaseField, RootFolder, }; use crate::models::{ - HorizontallyScrollableText, ScrollableText, StatefulList, StatefulTable, TabRoute, TabState, + BlockSelectionState, HorizontallyScrollableText, ScrollableText, StatefulList, StatefulTable, + TabRoute, TabState, }; use crate::network::radarr_network::RadarrEvent; -pub struct RadarrData { +pub struct RadarrData<'a> { pub root_folders: StatefulTable, pub disk_space_vec: Vec, pub version: String, @@ -24,7 +25,7 @@ pub struct RadarrData { pub minimum_availability_list: StatefulList, pub quality_profile_list: StatefulList, pub root_folder_list: StatefulList, - pub selected_block: ActiveRadarrBlock, + pub selected_block: BlockSelectionState<'a, ActiveRadarrBlock>, pub downloads: StatefulTable, pub quality_profile_map: BiMap, pub tags_map: BiMap, @@ -57,7 +58,7 @@ pub struct RadarrData { pub is_filtering: bool, } -impl RadarrData { +impl<'a> RadarrData<'a> { pub fn reset_movie_collection_table(&mut self) { self.collection_movies = StatefulTable::default(); } @@ -226,8 +227,8 @@ impl RadarrData { } } -impl Default for RadarrData { - fn default() -> RadarrData { +impl<'a> Default for RadarrData<'a> { + fn default() -> RadarrData<'a> { RadarrData { root_folders: StatefulTable::default(), disk_space_vec: Vec::new(), @@ -239,7 +240,7 @@ impl Default for RadarrData { minimum_availability_list: StatefulList::default(), quality_profile_list: StatefulList::default(), root_folder_list: StatefulList::default(), - selected_block: ActiveRadarrBlock::AddMovieSelectRootFolder, + selected_block: BlockSelectionState::default(), filtered_movies: StatefulTable::default(), downloads: StatefulTable::default(), quality_profile_map: BiMap::default(), @@ -271,75 +272,79 @@ impl Default for RadarrData { add_list_exclusion: false, main_tabs: TabState::new(vec![ TabRoute { - title: "Library".to_owned(), + title: "Library", route: ActiveRadarrBlock::Movies.into(), - help: String::default(), - contextual_help: Some(" add | edit | delete | search | filter | refresh | update all | details | cancel filter" - .to_owned()), + help: "", + contextual_help: Some(" add | edit | delete | search | filter | refresh | update all | details | cancel filter"), }, TabRoute { - title: "Downloads".to_owned(), + title: "Downloads", route: ActiveRadarrBlock::Downloads.into(), - help: String::default(), - contextual_help: Some(" refresh | delete".to_owned()), + help: "", + contextual_help: Some(" refresh | delete"), }, TabRoute { - title: "Collections".to_owned(), + title: "Collections", route: ActiveRadarrBlock::Collections.into(), - help: String::default(), - contextual_help: Some(" search | edit | filter | refresh | update all | details | cancel filter" - .to_owned()), + help: "", + contextual_help: Some(" search | edit | filter | refresh | update all | details | cancel filter"), }, TabRoute { - title: "Root Folders".to_owned(), + title: "Root Folders", route: ActiveRadarrBlock::RootFolders.into(), - help: String::default(), - contextual_help: Some(" add | delete | refresh".to_owned()), + help: "", + contextual_help: Some(" add | delete | refresh"), }, + TabRoute { + title: "System", + route: ActiveRadarrBlock::System.into(), + help: "", + contextual_help: Some(" select menu item | go back to menu selection") + } ]), movie_info_tabs: TabState::new(vec![ TabRoute { - title: "Details".to_owned(), + title: "Details", route: ActiveRadarrBlock::MovieDetails.into(), - help: " refresh | update | edit | auto search | close".to_owned(), + help: " refresh | update | edit | auto search | close", contextual_help: None }, TabRoute { - title: "History".to_owned(), + title: "History", route: ActiveRadarrBlock::MovieHistory.into(), - help: " refresh | update | edit | auto search | close".to_owned(), + help: " refresh | update | edit | auto search | close", contextual_help: None }, TabRoute { - title: "File".to_owned(), + title: "File", route: ActiveRadarrBlock::FileInfo.into(), - help: " refresh | update | edit | auto search | close".to_owned(), + help: " refresh | update | edit | auto search | close", contextual_help: None, }, TabRoute { - title: "Cast".to_owned(), + title: "Cast", route: ActiveRadarrBlock::Cast.into(), - help: " refresh | update | edit | auto search | close".to_owned(), + help: " refresh | update | edit | auto search | close", contextual_help: None, }, TabRoute { - title: "Crew".to_owned(), + title: "Crew", route: ActiveRadarrBlock::Crew.into(), - help: " refresh | update | edit | auto search | close".to_owned(), + help: " refresh | update | edit | auto search | close", contextual_help: None, }, TabRoute { - title: "Manual Search".to_owned(), + title: "Manual Search", route: ActiveRadarrBlock::ManualSearch.into(), - help: " refresh | update | edit | sort | auto search | close".to_owned(), - contextual_help: Some(" details".to_owned()) + help: " refresh | update | edit | sort | auto search | close", + contextual_help: Some(" details") } ]), } } } -#[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)] pub enum ActiveRadarrBlock { AddMovieAlreadyInLibrary, AddMovieSearchInput, @@ -387,8 +392,10 @@ pub enum ActiveRadarrBlock { ManualSearchConfirmPrompt, MovieDetails, MovieHistory, + #[default] Movies, RootFolders, + System, UpdateAndScanPrompt, UpdateAllCollectionsPrompt, UpdateAllMoviesPrompt, @@ -410,6 +417,14 @@ pub const ADD_MOVIE_BLOCKS: [ActiveRadarrBlock; 10] = [ ActiveRadarrBlock::AddMovieAlreadyInLibrary, ActiveRadarrBlock::AddMovieTagsInput, ]; +pub const ADD_MOVIE_SELECTION_BLOCKS: [ActiveRadarrBlock; 6] = [ + ActiveRadarrBlock::AddMovieSelectRootFolder, + ActiveRadarrBlock::AddMovieSelectMonitor, + ActiveRadarrBlock::AddMovieSelectMinimumAvailability, + ActiveRadarrBlock::AddMovieSelectQualityProfile, + ActiveRadarrBlock::AddMovieTagsInput, + ActiveRadarrBlock::AddMovieConfirmPrompt, +]; pub const EDIT_COLLECTION_BLOCKS: [ActiveRadarrBlock; 7] = [ ActiveRadarrBlock::EditCollectionPrompt, ActiveRadarrBlock::EditCollectionConfirmPrompt, @@ -419,6 +434,14 @@ pub const EDIT_COLLECTION_BLOCKS: [ActiveRadarrBlock; 7] = [ ActiveRadarrBlock::EditCollectionToggleSearchOnAdd, ActiveRadarrBlock::EditCollectionToggleMonitored, ]; +pub const EDIT_COLLECTION_SELECTION_BLOCKS: [ActiveRadarrBlock; 6] = [ + ActiveRadarrBlock::EditCollectionToggleMonitored, + ActiveRadarrBlock::EditCollectionSelectMinimumAvailability, + ActiveRadarrBlock::EditCollectionSelectQualityProfile, + ActiveRadarrBlock::EditCollectionRootFolderPathInput, + ActiveRadarrBlock::EditCollectionToggleSearchOnAdd, + ActiveRadarrBlock::EditCollectionConfirmPrompt, +]; pub const EDIT_MOVIE_BLOCKS: [ActiveRadarrBlock; 7] = [ ActiveRadarrBlock::EditMoviePrompt, ActiveRadarrBlock::EditMovieConfirmPrompt, @@ -428,6 +451,14 @@ pub const EDIT_MOVIE_BLOCKS: [ActiveRadarrBlock; 7] = [ ActiveRadarrBlock::EditMovieTagsInput, ActiveRadarrBlock::EditMovieToggleMonitored, ]; +pub const EDIT_MOVIE_SELECTION_BLOCKS: [ActiveRadarrBlock; 6] = [ + ActiveRadarrBlock::EditMovieToggleMonitored, + ActiveRadarrBlock::EditMovieSelectMinimumAvailability, + ActiveRadarrBlock::EditMovieSelectQualityProfile, + ActiveRadarrBlock::EditMoviePathInput, + ActiveRadarrBlock::EditMovieTagsInput, + ActiveRadarrBlock::EditMovieConfirmPrompt, +]; pub const MOVIE_DETAILS_BLOCKS: [ActiveRadarrBlock; 10] = [ ActiveRadarrBlock::MovieDetails, ActiveRadarrBlock::MovieHistory, @@ -458,141 +489,11 @@ pub const DELETE_MOVIE_BLOCKS: [ActiveRadarrBlock; 4] = [ ActiveRadarrBlock::DeleteMovieToggleDeleteFile, ActiveRadarrBlock::DeleteMovieToggleAddListExclusion, ]; - -impl ActiveRadarrBlock { - pub fn next_add_movie_prompt_block(&self) -> Self { - match self { - ActiveRadarrBlock::AddMovieSelectRootFolder => ActiveRadarrBlock::AddMovieSelectMonitor, - ActiveRadarrBlock::AddMovieSelectMonitor => { - ActiveRadarrBlock::AddMovieSelectMinimumAvailability - } - ActiveRadarrBlock::AddMovieSelectMinimumAvailability => { - ActiveRadarrBlock::AddMovieSelectQualityProfile - } - ActiveRadarrBlock::AddMovieSelectQualityProfile => ActiveRadarrBlock::AddMovieTagsInput, - ActiveRadarrBlock::AddMovieTagsInput => ActiveRadarrBlock::AddMovieConfirmPrompt, - _ => ActiveRadarrBlock::AddMovieSelectRootFolder, - } - } - - pub fn next_edit_movie_prompt_block(&self) -> Self { - match self { - ActiveRadarrBlock::EditMovieToggleMonitored => { - ActiveRadarrBlock::EditMovieSelectMinimumAvailability - } - ActiveRadarrBlock::EditMovieSelectMinimumAvailability => { - ActiveRadarrBlock::EditMovieSelectQualityProfile - } - ActiveRadarrBlock::EditMovieSelectQualityProfile => ActiveRadarrBlock::EditMoviePathInput, - ActiveRadarrBlock::EditMoviePathInput => ActiveRadarrBlock::EditMovieTagsInput, - ActiveRadarrBlock::EditMovieTagsInput => ActiveRadarrBlock::EditMovieConfirmPrompt, - _ => ActiveRadarrBlock::EditMovieToggleMonitored, - } - } - - pub fn next_edit_collection_prompt_block(&self) -> Self { - match self { - ActiveRadarrBlock::EditCollectionToggleMonitored => { - ActiveRadarrBlock::EditCollectionSelectMinimumAvailability - } - ActiveRadarrBlock::EditCollectionSelectMinimumAvailability => { - ActiveRadarrBlock::EditCollectionSelectQualityProfile - } - ActiveRadarrBlock::EditCollectionSelectQualityProfile => { - ActiveRadarrBlock::EditCollectionRootFolderPathInput - } - ActiveRadarrBlock::EditCollectionRootFolderPathInput => { - ActiveRadarrBlock::EditCollectionToggleSearchOnAdd - } - ActiveRadarrBlock::EditCollectionToggleSearchOnAdd => { - ActiveRadarrBlock::EditCollectionConfirmPrompt - } - _ => ActiveRadarrBlock::EditCollectionToggleMonitored, - } - } - - pub fn next_delete_movie_prompt_block(&self) -> Self { - match self { - ActiveRadarrBlock::DeleteMovieToggleDeleteFile => { - ActiveRadarrBlock::DeleteMovieToggleAddListExclusion - } - ActiveRadarrBlock::DeleteMovieToggleAddListExclusion => { - ActiveRadarrBlock::DeleteMovieConfirmPrompt - } - ActiveRadarrBlock::DeleteMovieConfirmPrompt => ActiveRadarrBlock::DeleteMovieToggleDeleteFile, - _ => ActiveRadarrBlock::DeleteMovieToggleDeleteFile, - } - } - - pub fn previous_add_movie_prompt_block(&self) -> Self { - match self { - ActiveRadarrBlock::AddMovieSelectRootFolder => ActiveRadarrBlock::AddMovieConfirmPrompt, - ActiveRadarrBlock::AddMovieSelectMonitor => ActiveRadarrBlock::AddMovieSelectRootFolder, - ActiveRadarrBlock::AddMovieSelectMinimumAvailability => { - ActiveRadarrBlock::AddMovieSelectMonitor - } - ActiveRadarrBlock::AddMovieSelectQualityProfile => { - ActiveRadarrBlock::AddMovieSelectMinimumAvailability - } - ActiveRadarrBlock::AddMovieTagsInput => ActiveRadarrBlock::AddMovieSelectQualityProfile, - ActiveRadarrBlock::AddMovieConfirmPrompt => ActiveRadarrBlock::AddMovieTagsInput, - _ => ActiveRadarrBlock::AddMovieSelectRootFolder, - } - } - - pub fn previous_edit_movie_prompt_block(&self) -> Self { - match self { - ActiveRadarrBlock::EditMovieToggleMonitored => ActiveRadarrBlock::EditMovieConfirmPrompt, - ActiveRadarrBlock::EditMovieSelectMinimumAvailability => { - ActiveRadarrBlock::EditMovieToggleMonitored - } - ActiveRadarrBlock::EditMovieSelectQualityProfile => { - ActiveRadarrBlock::EditMovieSelectMinimumAvailability - } - ActiveRadarrBlock::EditMoviePathInput => ActiveRadarrBlock::EditMovieSelectQualityProfile, - ActiveRadarrBlock::EditMovieTagsInput => ActiveRadarrBlock::EditMoviePathInput, - ActiveRadarrBlock::EditMovieConfirmPrompt => ActiveRadarrBlock::EditMovieTagsInput, - _ => ActiveRadarrBlock::EditMovieToggleMonitored, - } - } - - pub fn previous_edit_collection_prompt_block(&self) -> Self { - match self { - ActiveRadarrBlock::EditCollectionToggleMonitored => { - ActiveRadarrBlock::EditCollectionConfirmPrompt - } - ActiveRadarrBlock::EditCollectionSelectMinimumAvailability => { - ActiveRadarrBlock::EditCollectionToggleMonitored - } - ActiveRadarrBlock::EditCollectionSelectQualityProfile => { - ActiveRadarrBlock::EditCollectionSelectMinimumAvailability - } - ActiveRadarrBlock::EditCollectionRootFolderPathInput => { - ActiveRadarrBlock::EditCollectionSelectQualityProfile - } - ActiveRadarrBlock::EditCollectionToggleSearchOnAdd => { - ActiveRadarrBlock::EditCollectionRootFolderPathInput - } - ActiveRadarrBlock::EditCollectionConfirmPrompt => { - ActiveRadarrBlock::EditCollectionToggleSearchOnAdd - } - _ => ActiveRadarrBlock::EditCollectionToggleMonitored, - } - } - - pub fn previous_delete_movie_prompt_block(&self) -> Self { - match self { - ActiveRadarrBlock::DeleteMovieToggleDeleteFile => ActiveRadarrBlock::DeleteMovieConfirmPrompt, - ActiveRadarrBlock::DeleteMovieToggleAddListExclusion => { - ActiveRadarrBlock::DeleteMovieToggleDeleteFile - } - ActiveRadarrBlock::DeleteMovieConfirmPrompt => { - ActiveRadarrBlock::DeleteMovieToggleAddListExclusion - } - _ => ActiveRadarrBlock::DeleteMovieToggleDeleteFile, - } - } -} +pub const DELETE_MOVIE_SELECTION_BLOCKS: [ActiveRadarrBlock; 3] = [ + ActiveRadarrBlock::DeleteMovieToggleDeleteFile, + ActiveRadarrBlock::DeleteMovieToggleAddListExclusion, + ActiveRadarrBlock::DeleteMovieConfirmPrompt, +]; impl From for Route { fn from(active_radarr_block: ActiveRadarrBlock) -> Route { @@ -606,7 +507,7 @@ impl From<(ActiveRadarrBlock, Option)> for Route { } } -impl App { +impl<'a> App<'a> { pub(super) async fn dispatch_by_radarr_block(&mut self, active_radarr_block: &ActiveRadarrBlock) { match active_radarr_block { ActiveRadarrBlock::Collections => { @@ -773,7 +674,7 @@ pub mod radarr_test_utils { }; use crate::models::ScrollableText; - pub fn create_test_radarr_data() -> RadarrData { + pub fn create_test_radarr_data<'a>() -> RadarrData<'a> { let mut radarr_data = RadarrData { is_searching: true, is_filtering: true, @@ -905,9 +806,9 @@ mod tests { use crate::models::radarr_models::{ Collection, MinimumAvailability, Monitor, Movie, RootFolder, }; - use crate::models::HorizontallyScrollableText; use crate::models::Route; use crate::models::StatefulTable; + use crate::models::{BlockSelectionState, HorizontallyScrollableText}; #[test] fn test_from_tuple_to_route_with_context() { @@ -1149,10 +1050,7 @@ mod tests { assert!(radarr_data.minimum_availability_list.items.is_empty()); assert!(radarr_data.quality_profile_list.items.is_empty()); assert!(radarr_data.root_folder_list.items.is_empty()); - assert_eq!( - radarr_data.selected_block, - ActiveRadarrBlock::AddMovieSelectRootFolder - ); + assert_eq!(radarr_data.selected_block, BlockSelectionState::default()); assert!(radarr_data.filtered_movies.items.is_empty()); assert!(radarr_data.downloads.items.is_empty()); assert!(radarr_data.quality_profile_map.is_empty()); @@ -1183,7 +1081,7 @@ mod tests { assert!(!radarr_data.delete_movie_files); assert!(!radarr_data.add_list_exclusion); - assert_eq!(radarr_data.main_tabs.tabs.len(), 4); + assert_eq!(radarr_data.main_tabs.tabs.len(), 5); assert_str_eq!(radarr_data.main_tabs.tabs[0].title, "Library"); assert_eq!( @@ -1192,7 +1090,7 @@ mod tests { ); assert!(radarr_data.main_tabs.tabs[0].help.is_empty()); assert_eq!(radarr_data.main_tabs.tabs[0].contextual_help, - Some(" add | edit | delete | search | filter | refresh | update all | details | cancel filter".to_owned())); + Some(" add | edit | delete | search | filter | refresh | update all | details | cancel filter")); assert_str_eq!(radarr_data.main_tabs.tabs[1].title, "Downloads"); assert_eq!( @@ -1202,7 +1100,7 @@ mod tests { assert!(radarr_data.main_tabs.tabs[1].help.is_empty()); assert_eq!( radarr_data.main_tabs.tabs[1].contextual_help, - Some(" refresh | delete".to_owned()) + Some(" refresh | delete") ); assert_str_eq!(radarr_data.main_tabs.tabs[2].title, "Collections"); @@ -1212,7 +1110,7 @@ mod tests { ); assert!(radarr_data.main_tabs.tabs[2].help.is_empty()); assert_eq!(radarr_data.main_tabs.tabs[2].contextual_help, - Some(" search | edit | filter | refresh | update all | details | cancel filter".to_owned())); + Some(" search | edit | filter | refresh | update all | details | cancel filter")); assert_str_eq!(radarr_data.main_tabs.tabs[3].title, "Root Folders"); assert_eq!( @@ -1222,7 +1120,18 @@ mod tests { assert!(radarr_data.main_tabs.tabs[3].help.is_empty()); assert_eq!( radarr_data.main_tabs.tabs[3].contextual_help, - Some(" add | delete | refresh".to_owned()) + Some(" add | delete | refresh") + ); + + assert_str_eq!(radarr_data.main_tabs.tabs[4].title, "System"); + assert_eq!( + radarr_data.main_tabs.tabs[4].route, + ActiveRadarrBlock::System.into() + ); + assert!(radarr_data.main_tabs.tabs[4].help.is_empty()); + assert_eq!( + radarr_data.main_tabs.tabs[4].contextual_help, + Some(" select menu item | go back to menu selection") ); assert_eq!(radarr_data.movie_info_tabs.tabs.len(), 6); @@ -1303,7 +1212,7 @@ mod tests { ); assert_eq!( radarr_data.movie_info_tabs.tabs[5].contextual_help, - Some(" details".to_owned()) + Some(" details") ); } } @@ -1311,264 +1220,116 @@ mod tests { mod active_radarr_block_tests { use pretty_assertions::assert_eq; - use crate::app::radarr::ActiveRadarrBlock; + use crate::app::radarr::{ + ActiveRadarrBlock, ADD_MOVIE_SELECTION_BLOCKS, DELETE_MOVIE_SELECTION_BLOCKS, + EDIT_COLLECTION_SELECTION_BLOCKS, EDIT_MOVIE_SELECTION_BLOCKS, + }; #[test] - fn test_next_add_movie_prompt_block() { - let active_block = ActiveRadarrBlock::AddMovieSelectRootFolder.next_add_movie_prompt_block(); - - assert_eq!(active_block, ActiveRadarrBlock::AddMovieSelectMonitor); - - let active_block = active_block.next_add_movie_prompt_block(); + fn test_add_movie_prompt_block_order() { + let mut add_movie_block_iter = ADD_MOVIE_SELECTION_BLOCKS.iter(); assert_eq!( - active_block, - ActiveRadarrBlock::AddMovieSelectMinimumAvailability + add_movie_block_iter.next().unwrap(), + &ActiveRadarrBlock::AddMovieSelectRootFolder ); - - let active_block = active_block.next_add_movie_prompt_block(); - assert_eq!( - active_block, - ActiveRadarrBlock::AddMovieSelectQualityProfile + add_movie_block_iter.next().unwrap(), + &ActiveRadarrBlock::AddMovieSelectMonitor ); - - let active_block = active_block.next_add_movie_prompt_block(); - - assert_eq!(active_block, ActiveRadarrBlock::AddMovieTagsInput); - - let active_block = active_block.next_add_movie_prompt_block(); - - assert_eq!(active_block, ActiveRadarrBlock::AddMovieConfirmPrompt); - - let active_block = active_block.next_add_movie_prompt_block(); - - assert_eq!(active_block, ActiveRadarrBlock::AddMovieSelectRootFolder); - } - - #[test] - fn test_next_edit_movie_prompt_block() { - let active_block = ActiveRadarrBlock::EditMovieToggleMonitored.next_edit_movie_prompt_block(); - assert_eq!( - active_block, - ActiveRadarrBlock::EditMovieSelectMinimumAvailability + add_movie_block_iter.next().unwrap(), + &ActiveRadarrBlock::AddMovieSelectMinimumAvailability ); - - let active_block = active_block.next_edit_movie_prompt_block(); - assert_eq!( - active_block, - ActiveRadarrBlock::EditMovieSelectQualityProfile + add_movie_block_iter.next().unwrap(), + &ActiveRadarrBlock::AddMovieSelectQualityProfile ); - - let active_block = active_block.next_edit_movie_prompt_block(); - - assert_eq!(active_block, ActiveRadarrBlock::EditMoviePathInput); - - let active_block = active_block.next_edit_movie_prompt_block(); - - assert_eq!(active_block, ActiveRadarrBlock::EditMovieTagsInput); - - let active_block = active_block.next_edit_movie_prompt_block(); - - assert_eq!(active_block, ActiveRadarrBlock::EditMovieConfirmPrompt); - - let active_block = active_block.next_edit_movie_prompt_block(); - - assert_eq!(active_block, ActiveRadarrBlock::EditMovieToggleMonitored); - } - - #[test] - fn test_next_edit_collection_prompt_block() { - let active_block = - ActiveRadarrBlock::EditCollectionToggleMonitored.next_edit_collection_prompt_block(); - assert_eq!( - active_block, - ActiveRadarrBlock::EditCollectionSelectMinimumAvailability + add_movie_block_iter.next().unwrap(), + &ActiveRadarrBlock::AddMovieTagsInput ); - - let active_block = active_block.next_edit_collection_prompt_block(); - assert_eq!( - active_block, - ActiveRadarrBlock::EditCollectionSelectQualityProfile - ); - - let active_block = active_block.next_edit_collection_prompt_block(); - - assert_eq!( - active_block, - ActiveRadarrBlock::EditCollectionRootFolderPathInput - ); - - let active_block = active_block.next_edit_collection_prompt_block(); - - assert_eq!( - active_block, - ActiveRadarrBlock::EditCollectionToggleSearchOnAdd - ); - - let active_block = active_block.next_edit_collection_prompt_block(); - - assert_eq!(active_block, ActiveRadarrBlock::EditCollectionConfirmPrompt); - - let active_block = active_block.next_edit_collection_prompt_block(); - - assert_eq!( - active_block, - ActiveRadarrBlock::EditCollectionToggleMonitored + add_movie_block_iter.next().unwrap(), + &ActiveRadarrBlock::AddMovieConfirmPrompt ); } #[test] - fn test_next_delete_movie_prompt_block() { - let active_block = - ActiveRadarrBlock::DeleteMovieToggleDeleteFile.next_delete_movie_prompt_block(); + fn test_edit_movie_prompt_block_order() { + let mut edit_movie_block_iter = EDIT_MOVIE_SELECTION_BLOCKS.iter(); assert_eq!( - active_block, - ActiveRadarrBlock::DeleteMovieToggleAddListExclusion + edit_movie_block_iter.next().unwrap(), + &ActiveRadarrBlock::EditMovieToggleMonitored ); - - let active_block = active_block.next_delete_movie_prompt_block(); - - assert_eq!(active_block, ActiveRadarrBlock::DeleteMovieConfirmPrompt); - - let active_block = active_block.next_delete_movie_prompt_block(); - - assert_eq!(active_block, ActiveRadarrBlock::DeleteMovieToggleDeleteFile); - } - - #[test] - fn test_previous_add_movie_prompt_block() { - let active_block = - ActiveRadarrBlock::AddMovieSelectRootFolder.previous_add_movie_prompt_block(); - - assert_eq!(active_block, ActiveRadarrBlock::AddMovieConfirmPrompt); - - let active_block = active_block.previous_add_movie_prompt_block(); - - assert_eq!(active_block, ActiveRadarrBlock::AddMovieTagsInput); - - let active_block = active_block.previous_add_movie_prompt_block(); - assert_eq!( - active_block, - ActiveRadarrBlock::AddMovieSelectQualityProfile + edit_movie_block_iter.next().unwrap(), + &ActiveRadarrBlock::EditMovieSelectMinimumAvailability ); - - let active_block = active_block.previous_add_movie_prompt_block(); - assert_eq!( - active_block, - ActiveRadarrBlock::AddMovieSelectMinimumAvailability + edit_movie_block_iter.next().unwrap(), + &ActiveRadarrBlock::EditMovieSelectQualityProfile ); - - let active_block = active_block.previous_add_movie_prompt_block(); - - assert_eq!(active_block, ActiveRadarrBlock::AddMovieSelectMonitor); - - let active_block = active_block.previous_add_movie_prompt_block(); - - assert_eq!(active_block, ActiveRadarrBlock::AddMovieSelectRootFolder); - } - - #[test] - fn test_previous_edit_movie_prompt_block() { - let active_block = - ActiveRadarrBlock::EditMovieToggleMonitored.previous_edit_movie_prompt_block(); - - assert_eq!(active_block, ActiveRadarrBlock::EditMovieConfirmPrompt); - - let active_block = active_block.previous_edit_movie_prompt_block(); - - assert_eq!(active_block, ActiveRadarrBlock::EditMovieTagsInput); - - let active_block = active_block.previous_edit_movie_prompt_block(); - - assert_eq!(active_block, ActiveRadarrBlock::EditMoviePathInput); - - let active_block = active_block.previous_edit_movie_prompt_block(); - assert_eq!( - active_block, - ActiveRadarrBlock::EditMovieSelectQualityProfile + edit_movie_block_iter.next().unwrap(), + &ActiveRadarrBlock::EditMoviePathInput ); - - let active_block = active_block.previous_edit_movie_prompt_block(); - assert_eq!( - active_block, - ActiveRadarrBlock::EditMovieSelectMinimumAvailability + edit_movie_block_iter.next().unwrap(), + &ActiveRadarrBlock::EditMovieTagsInput ); - - let active_block = active_block.previous_edit_movie_prompt_block(); - - assert_eq!(active_block, ActiveRadarrBlock::EditMovieToggleMonitored); - } - - #[test] - fn test_previous_edit_collection_prompt_block() { - let active_block = - ActiveRadarrBlock::EditCollectionToggleMonitored.previous_edit_collection_prompt_block(); - - assert_eq!(active_block, ActiveRadarrBlock::EditCollectionConfirmPrompt); - - let active_block = active_block.previous_edit_collection_prompt_block(); - assert_eq!( - active_block, - ActiveRadarrBlock::EditCollectionToggleSearchOnAdd - ); - - let active_block = active_block.previous_edit_collection_prompt_block(); - - assert_eq!( - active_block, - ActiveRadarrBlock::EditCollectionRootFolderPathInput - ); - - let active_block = active_block.previous_edit_collection_prompt_block(); - - assert_eq!( - active_block, - ActiveRadarrBlock::EditCollectionSelectQualityProfile - ); - - let active_block = active_block.previous_edit_collection_prompt_block(); - - assert_eq!( - active_block, - ActiveRadarrBlock::EditCollectionSelectMinimumAvailability - ); - - let active_block = active_block.previous_edit_collection_prompt_block(); - - assert_eq!( - active_block, - ActiveRadarrBlock::EditCollectionToggleMonitored + edit_movie_block_iter.next().unwrap(), + &ActiveRadarrBlock::EditMovieConfirmPrompt ); } #[test] - fn test_previous_delete_movie_prompt_block() { - let active_block = - ActiveRadarrBlock::DeleteMovieToggleDeleteFile.previous_delete_movie_prompt_block(); - - assert_eq!(active_block, ActiveRadarrBlock::DeleteMovieConfirmPrompt); - - let active_block = active_block.previous_delete_movie_prompt_block(); + fn test_edit_collection_prompt_block_order() { + let mut edit_collection_block_iter = EDIT_COLLECTION_SELECTION_BLOCKS.iter(); assert_eq!( - active_block, - ActiveRadarrBlock::DeleteMovieToggleAddListExclusion + edit_collection_block_iter.next().unwrap(), + &ActiveRadarrBlock::EditCollectionToggleMonitored ); + assert_eq!( + edit_collection_block_iter.next().unwrap(), + &ActiveRadarrBlock::EditCollectionSelectMinimumAvailability + ); + assert_eq!( + edit_collection_block_iter.next().unwrap(), + &ActiveRadarrBlock::EditCollectionSelectQualityProfile + ); + assert_eq!( + edit_collection_block_iter.next().unwrap(), + &ActiveRadarrBlock::EditCollectionRootFolderPathInput + ); + assert_eq!( + edit_collection_block_iter.next().unwrap(), + &ActiveRadarrBlock::EditCollectionToggleSearchOnAdd + ); + assert_eq!( + edit_collection_block_iter.next().unwrap(), + &ActiveRadarrBlock::EditCollectionConfirmPrompt + ); + } - let active_block = active_block.previous_delete_movie_prompt_block(); - - assert_eq!(active_block, ActiveRadarrBlock::DeleteMovieToggleDeleteFile); + #[test] + fn test_delete_movie_prompt_block_order() { + let mut delete_movie_block_iter = DELETE_MOVIE_SELECTION_BLOCKS.iter(); + assert_eq!( + delete_movie_block_iter.next().unwrap(), + &ActiveRadarrBlock::DeleteMovieToggleDeleteFile + ); + assert_eq!( + delete_movie_block_iter.next().unwrap(), + &ActiveRadarrBlock::DeleteMovieToggleAddListExclusion + ); + assert_eq!( + delete_movie_block_iter.next().unwrap(), + &ActiveRadarrBlock::DeleteMovieConfirmPrompt + ); } } @@ -2120,7 +1881,7 @@ mod tests { assert!(!app.data.radarr_data.collection_movies.items.is_empty()); } - fn construct_app_unit() -> (App, mpsc::Receiver) { + fn construct_app_unit<'a>() -> (App<'a>, mpsc::Receiver) { let (sync_network_tx, sync_network_rx) = mpsc::channel::(500); let mut app = App { network_tx: Some(sync_network_tx), diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs index 4d16b63..631a480 100644 --- a/src/handlers/mod.rs +++ b/src/handlers/mod.rs @@ -7,7 +7,7 @@ use crate::models::{HorizontallyScrollableText, Route}; mod radarr_handlers; -pub trait KeyEventHandler<'a, T: Into> { +pub trait KeyEventHandler<'a, 'b, T: Into> { fn handle_key_event(&mut self) { let key = self.get_key(); match key { @@ -29,7 +29,7 @@ pub trait KeyEventHandler<'a, T: Into> { self.handle_key_event(); } - fn with(key: &'a Key, app: &'a mut App, active_block: &'a T, context: &'a Option) -> Self; + fn with(key: &'a Key, app: &'a mut App<'b>, active_block: &'a T, context: &'a Option) -> Self; fn get_key(&self) -> &Key; fn handle_scroll_up(&mut self); fn handle_scroll_down(&mut self); @@ -42,19 +42,19 @@ pub trait KeyEventHandler<'a, T: Into> { fn handle_char_key_event(&mut self); } -pub fn handle_events(key: Key, app: &mut App) { +pub fn handle_events(key: Key, app: &mut App<'_>) { if let Route::Radarr(active_radarr_block, context) = *app.get_current_route() { RadarrHandler::with(&key, app, &active_radarr_block, &context).handle() } } -fn handle_clear_errors(app: &mut App) { +fn handle_clear_errors(app: &mut App<'_>) { if !app.error.text.is_empty() { app.error = HorizontallyScrollableText::default(); } } -fn handle_prompt_toggle(app: &mut App, key: &Key) { +fn handle_prompt_toggle(app: &mut App<'_>, key: &Key) { match key { _ if *key == DEFAULT_KEYBINDINGS.left.key || *key == DEFAULT_KEYBINDINGS.right.key => { if let Route::Radarr(_, _) = *app.get_current_route() { diff --git a/src/handlers/radarr_handlers/add_movie_handler.rs b/src/handlers/radarr_handlers/add_movie_handler.rs index 4d9aa42..10b0cec 100644 --- a/src/handlers/radarr_handlers/add_movie_handler.rs +++ b/src/handlers/radarr_handlers/add_movie_handler.rs @@ -1,24 +1,24 @@ use crate::app::key_binding::DEFAULT_KEYBINDINGS; -use crate::app::radarr::ActiveRadarrBlock; +use crate::app::radarr::{ActiveRadarrBlock, ADD_MOVIE_SELECTION_BLOCKS}; use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; -use crate::models::{Scrollable, StatefulTable}; +use crate::models::{BlockSelectionState, Scrollable, StatefulTable}; use crate::network::radarr_network::RadarrEvent; use crate::{handle_text_box_keys, handle_text_box_left_right_keys, App, Key}; -pub(super) struct AddMovieHandler<'a> { +pub(super) struct AddMovieHandler<'a, 'b> { key: &'a Key, - app: &'a mut App, + app: &'a mut App<'b>, active_radarr_block: &'a ActiveRadarrBlock, context: &'a Option, } -impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for AddMovieHandler<'a> { +impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for AddMovieHandler<'a, 'b> { fn with( key: &'a Key, - app: &'a mut App, + app: &'a mut App<'b>, active_block: &'a ActiveRadarrBlock, context: &'a Option, - ) -> AddMovieHandler<'a> { + ) -> AddMovieHandler<'a, 'b> { AddMovieHandler { key, app, @@ -51,14 +51,7 @@ impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for AddMovieHandler<'a> { ActiveRadarrBlock::AddMovieSelectRootFolder => { self.app.data.radarr_data.root_folder_list.scroll_up() } - ActiveRadarrBlock::AddMoviePrompt => { - self.app.data.radarr_data.selected_block = self - .app - .data - .radarr_data - .selected_block - .previous_add_movie_prompt_block() - } + ActiveRadarrBlock::AddMoviePrompt => self.app.data.radarr_data.selected_block.previous(), _ => (), } } @@ -83,14 +76,7 @@ impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for AddMovieHandler<'a> { ActiveRadarrBlock::AddMovieSelectRootFolder => { self.app.data.radarr_data.root_folder_list.scroll_down() } - ActiveRadarrBlock::AddMoviePrompt => { - self.app.data.radarr_data.selected_block = self - .app - .data - .radarr_data - .selected_block - .next_add_movie_prompt_block() - } + ActiveRadarrBlock::AddMoviePrompt => self.app.data.radarr_data.selected_block.next(), _ => (), } } @@ -220,31 +206,42 @@ impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for AddMovieHandler<'a> { .app .push_navigation_stack(ActiveRadarrBlock::AddMoviePrompt.into()); self.app.data.radarr_data.populate_preferences_lists(); - self.app.data.radarr_data.selected_block = ActiveRadarrBlock::AddMovieSelectRootFolder; + self.app.data.radarr_data.selected_block = + BlockSelectionState::new(&ADD_MOVIE_SELECTION_BLOCKS); } } - ActiveRadarrBlock::AddMoviePrompt => match self.app.data.radarr_data.selected_block { - ActiveRadarrBlock::AddMovieConfirmPrompt => { - if self.app.data.radarr_data.prompt_confirm { - self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::AddMovie); - } + ActiveRadarrBlock::AddMoviePrompt => { + match self.app.data.radarr_data.selected_block.get_active_block() { + ActiveRadarrBlock::AddMovieConfirmPrompt => { + if self.app.data.radarr_data.prompt_confirm { + self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::AddMovie); + } - self.app.pop_navigation_stack(); + self.app.pop_navigation_stack(); + } + ActiveRadarrBlock::AddMovieSelectMonitor + | ActiveRadarrBlock::AddMovieSelectMinimumAvailability + | ActiveRadarrBlock::AddMovieSelectQualityProfile + | ActiveRadarrBlock::AddMovieSelectRootFolder => self.app.push_navigation_stack( + ( + *self.app.data.radarr_data.selected_block.get_active_block(), + *self.context, + ) + .into(), + ), + ActiveRadarrBlock::AddMovieTagsInput => { + self.app.push_navigation_stack( + ( + *self.app.data.radarr_data.selected_block.get_active_block(), + *self.context, + ) + .into(), + ); + self.app.should_ignore_quit_key = true; + } + _ => (), } - ActiveRadarrBlock::AddMovieSelectMonitor - | ActiveRadarrBlock::AddMovieSelectMinimumAvailability - | ActiveRadarrBlock::AddMovieSelectQualityProfile - | ActiveRadarrBlock::AddMovieSelectRootFolder => self - .app - .push_navigation_stack((self.app.data.radarr_data.selected_block, *self.context).into()), - ActiveRadarrBlock::AddMovieTagsInput => { - self.app.push_navigation_stack( - (self.app.data.radarr_data.selected_block, *self.context).into(), - ); - self.app.should_ignore_quit_key = true; - } - _ => (), - }, + } ActiveRadarrBlock::AddMovieSelectMonitor | ActiveRadarrBlock::AddMovieSelectMinimumAvailability | ActiveRadarrBlock::AddMovieSelectQualityProfile @@ -321,6 +318,8 @@ mod tests { use rstest::rstest; use strum::IntoEnumIterator; + use crate::app::radarr::ADD_MOVIE_SELECTION_BLOCKS; + use crate::models::BlockSelectionState; use crate::{simple_stateful_iterable_vec, test_enum_scroll, test_iterable_scroll}; use super::*; @@ -375,19 +374,20 @@ mod tests { #[rstest] fn test_add_movie_prompt_scroll(#[values(Key::Up, Key::Down)] key: Key) { let mut app = App::default(); - app.data.radarr_data.selected_block = ActiveRadarrBlock::AddMovieSelectMinimumAvailability; + app.data.radarr_data.selected_block = BlockSelectionState::new(&ADD_MOVIE_SELECTION_BLOCKS); + app.data.radarr_data.selected_block.next(); AddMovieHandler::with(&key, &mut app, &ActiveRadarrBlock::AddMoviePrompt, &None).handle(); if key == Key::Up { assert_eq!( - app.data.radarr_data.selected_block, - ActiveRadarrBlock::AddMovieSelectMonitor + app.data.radarr_data.selected_block.get_active_block(), + &ActiveRadarrBlock::AddMovieSelectRootFolder ); } else { assert_eq!( - app.data.radarr_data.selected_block, - ActiveRadarrBlock::AddMovieSelectQualityProfile + app.data.radarr_data.selected_block.get_active_block(), + &ActiveRadarrBlock::AddMovieSelectMinimumAvailability ); } } @@ -513,7 +513,9 @@ mod tests { use pretty_assertions::{assert_eq, assert_str_eq}; use rstest::rstest; + use crate::app::radarr::ADD_MOVIE_SELECTION_BLOCKS; use crate::models::radarr_models::Movie; + use crate::models::BlockSelectionState; use crate::network::radarr_network::RadarrEvent; use super::*; @@ -564,8 +566,8 @@ mod tests { &ActiveRadarrBlock::AddMoviePrompt.into() ); assert_eq!( - app.data.radarr_data.selected_block, - ActiveRadarrBlock::AddMovieSelectRootFolder + app.data.radarr_data.selected_block.get_active_block(), + &ActiveRadarrBlock::AddMovieSelectRootFolder ); assert!(!app.data.radarr_data.monitor_list.items.is_empty()); assert!(!app @@ -636,7 +638,12 @@ mod tests { let mut app = App::default(); app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); app.push_navigation_stack(ActiveRadarrBlock::AddMoviePrompt.into()); - app.data.radarr_data.selected_block = ActiveRadarrBlock::AddMovieConfirmPrompt; + app.data.radarr_data.selected_block = BlockSelectionState::new(&ADD_MOVIE_SELECTION_BLOCKS); + app + .data + .radarr_data + .selected_block + .set_index(ADD_MOVIE_SELECTION_BLOCKS.len() - 1); AddMovieHandler::with( &SUBMIT_KEY, @@ -656,7 +663,12 @@ mod tests { app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); app.push_navigation_stack(ActiveRadarrBlock::AddMoviePrompt.into()); app.data.radarr_data.prompt_confirm = true; - app.data.radarr_data.selected_block = ActiveRadarrBlock::AddMovieConfirmPrompt; + app.data.radarr_data.selected_block = BlockSelectionState::new(&ADD_MOVIE_SELECTION_BLOCKS); + app + .data + .radarr_data + .selected_block + .set_index(ADD_MOVIE_SELECTION_BLOCKS.len() - 1); AddMovieHandler::with( &SUBMIT_KEY, @@ -674,15 +686,14 @@ mod tests { } #[rstest] + #[case(ActiveRadarrBlock::AddMovieSelectRootFolder, 0)] + #[case(ActiveRadarrBlock::AddMovieSelectMonitor, 1)] + #[case(ActiveRadarrBlock::AddMovieSelectMinimumAvailability, 2)] + #[case(ActiveRadarrBlock::AddMovieSelectQualityProfile, 3)] + #[case(ActiveRadarrBlock::AddMovieTagsInput, 4)] fn test_add_movie_prompt_selected_block_submit( - #[values( - ActiveRadarrBlock::AddMovieSelectMonitor, - ActiveRadarrBlock::AddMovieSelectMinimumAvailability, - ActiveRadarrBlock::AddMovieSelectQualityProfile, - ActiveRadarrBlock::AddMovieSelectRootFolder, - ActiveRadarrBlock::AddMovieTagsInput - )] - selected_block: ActiveRadarrBlock, + #[case] selected_block: ActiveRadarrBlock, + #[case] index: usize, ) { let mut app = App::default(); app.push_navigation_stack( @@ -692,7 +703,8 @@ mod tests { ) .into(), ); - app.data.radarr_data.selected_block = selected_block; + app.data.radarr_data.selected_block = BlockSelectionState::new(&ADD_MOVIE_SELECTION_BLOCKS); + app.data.radarr_data.selected_block.set_index(index); AddMovieHandler::with( &SUBMIT_KEY, diff --git a/src/handlers/radarr_handlers/collection_details_handler.rs b/src/handlers/radarr_handlers/collection_details_handler.rs index c0f01cc..b1c3aaf 100644 --- a/src/handlers/radarr_handlers/collection_details_handler.rs +++ b/src/handlers/radarr_handlers/collection_details_handler.rs @@ -1,24 +1,26 @@ use crate::app::key_binding::DEFAULT_KEYBINDINGS; -use crate::app::radarr::ActiveRadarrBlock; +use crate::app::radarr::{ + ActiveRadarrBlock, ADD_MOVIE_SELECTION_BLOCKS, EDIT_COLLECTION_SELECTION_BLOCKS, +}; use crate::app::App; use crate::event::Key; use crate::handlers::KeyEventHandler; -use crate::models::Scrollable; +use crate::models::{BlockSelectionState, Scrollable}; -pub(super) struct CollectionDetailsHandler<'a> { +pub(super) struct CollectionDetailsHandler<'a, 'b> { key: &'a Key, - app: &'a mut App, + app: &'a mut App<'b>, active_radarr_block: &'a ActiveRadarrBlock, _context: &'a Option, } -impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for CollectionDetailsHandler<'a> { +impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for CollectionDetailsHandler<'a, 'b> { fn with( key: &'a Key, - app: &'a mut App, + app: &'a mut App<'b>, active_block: &'a ActiveRadarrBlock, _context: &'a Option, - ) -> CollectionDetailsHandler<'a> { + ) -> CollectionDetailsHandler<'a, 'b> { CollectionDetailsHandler { key, app, @@ -95,7 +97,8 @@ impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for CollectionDetailsHandler<'a> ) .into(), ); - self.app.data.radarr_data.selected_block = ActiveRadarrBlock::AddMovieSelectRootFolder; + self.app.data.radarr_data.selected_block = + BlockSelectionState::new(&ADD_MOVIE_SELECTION_BLOCKS); self.app.data.radarr_data.populate_preferences_lists(); } } @@ -124,7 +127,8 @@ impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for CollectionDetailsHandler<'a> .into(), ); self.app.data.radarr_data.populate_edit_collection_fields(); - self.app.data.radarr_data.selected_block = ActiveRadarrBlock::EditCollectionToggleMonitored; + self.app.data.radarr_data.selected_block = + BlockSelectionState::new(&EDIT_COLLECTION_SELECTION_BLOCKS); } } } @@ -182,7 +186,9 @@ mod tests { use bimap::BiMap; use pretty_assertions::assert_eq; + use crate::app::radarr::ADD_MOVIE_SELECTION_BLOCKS; use crate::models::radarr_models::Movie; + use crate::models::BlockSelectionState; use super::*; @@ -198,7 +204,12 @@ mod tests { .set_items(vec![CollectionMovie::default()]); app.data.radarr_data.quality_profile_map = BiMap::from_iter([(1, "B - Test 2".to_owned()), (0, "A - Test 1".to_owned())]); - app.data.radarr_data.selected_block = ActiveRadarrBlock::AddMovieConfirmPrompt; + app.data.radarr_data.selected_block = BlockSelectionState::new(&ADD_MOVIE_SELECTION_BLOCKS); + app + .data + .radarr_data + .selected_block + .set_index(ADD_MOVIE_SELECTION_BLOCKS.len() - 1); CollectionDetailsHandler::with( &SUBMIT_KEY, @@ -218,8 +229,8 @@ mod tests { ); assert!(!app.data.radarr_data.monitor_list.items.is_empty()); assert_eq!( - app.data.radarr_data.selected_block, - ActiveRadarrBlock::AddMovieSelectRootFolder + app.data.radarr_data.selected_block.get_active_block(), + &ActiveRadarrBlock::AddMovieSelectRootFolder ); assert!(!app .data @@ -329,7 +340,9 @@ mod tests { use crate::app::radarr::radarr_test_utils::create_test_radarr_data; use crate::app::radarr::RadarrData; + use crate::app::radarr::EDIT_COLLECTION_SELECTION_BLOCKS; use crate::models::radarr_models::{Collection, MinimumAvailability}; + use crate::models::BlockSelectionState; use crate::models::HorizontallyScrollableText; use crate::models::StatefulTable; use crate::test_edit_collection_key; diff --git a/src/handlers/radarr_handlers/delete_movie_handler.rs b/src/handlers/radarr_handlers/delete_movie_handler.rs index 1adecd3..1bf2bc0 100644 --- a/src/handlers/radarr_handlers/delete_movie_handler.rs +++ b/src/handlers/radarr_handlers/delete_movie_handler.rs @@ -4,17 +4,17 @@ use crate::event::Key; use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; use crate::network::radarr_network::RadarrEvent; -pub(super) struct DeleteMovieHandler<'a> { +pub(super) struct DeleteMovieHandler<'a, 'b> { key: &'a Key, - app: &'a mut App, + app: &'a mut App<'b>, active_radarr_block: &'a ActiveRadarrBlock, _context: &'a Option, } -impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for DeleteMovieHandler<'a> { +impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for DeleteMovieHandler<'a, 'b> { fn with( key: &'a Key, - app: &'a mut App, + app: &'a mut App<'b>, active_block: &'a ActiveRadarrBlock, _context: &'a Option, ) -> Self { @@ -32,23 +32,13 @@ impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for DeleteMovieHandler<'a> { fn handle_scroll_up(&mut self) { if *self.active_radarr_block == ActiveRadarrBlock::DeleteMoviePrompt { - self.app.data.radarr_data.selected_block = self - .app - .data - .radarr_data - .selected_block - .previous_delete_movie_prompt_block(); + self.app.data.radarr_data.selected_block.previous(); } } fn handle_scroll_down(&mut self) { if *self.active_radarr_block == ActiveRadarrBlock::DeleteMoviePrompt { - self.app.data.radarr_data.selected_block = self - .app - .data - .radarr_data - .selected_block - .next_delete_movie_prompt_block(); + self.app.data.radarr_data.selected_block.next(); } } @@ -66,7 +56,7 @@ impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for DeleteMovieHandler<'a> { fn handle_submit(&mut self) { if self.active_radarr_block == &ActiveRadarrBlock::DeleteMoviePrompt { - match self.app.data.radarr_data.selected_block { + match self.app.data.radarr_data.selected_block.get_active_block() { ActiveRadarrBlock::DeleteMovieConfirmPrompt => { if self.app.data.radarr_data.prompt_confirm { self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::DeleteMovie); @@ -114,25 +104,30 @@ mod tests { use pretty_assertions::assert_eq; use rstest::rstest; + use crate::app::radarr::DELETE_MOVIE_SELECTION_BLOCKS; + use crate::models::BlockSelectionState; + use super::*; #[rstest] fn test_delete_movie_prompt_scroll(#[values(Key::Up, Key::Down)] key: Key) { let mut app = App::default(); - app.data.radarr_data.selected_block = ActiveRadarrBlock::DeleteMovieToggleAddListExclusion; + app.data.radarr_data.selected_block = + BlockSelectionState::new(&DELETE_MOVIE_SELECTION_BLOCKS); + app.data.radarr_data.selected_block.next(); DeleteMovieHandler::with(&key, &mut app, &ActiveRadarrBlock::DeleteMoviePrompt, &None) .handle(); if key == Key::Up { assert_eq!( - app.data.radarr_data.selected_block, - ActiveRadarrBlock::DeleteMovieToggleDeleteFile + app.data.radarr_data.selected_block.get_active_block(), + &ActiveRadarrBlock::DeleteMovieToggleDeleteFile ); } else { assert_eq!( - app.data.radarr_data.selected_block, - ActiveRadarrBlock::DeleteMovieConfirmPrompt + app.data.radarr_data.selected_block.get_active_block(), + &ActiveRadarrBlock::DeleteMovieConfirmPrompt ); } } @@ -162,6 +157,8 @@ mod tests { mod test_handle_submit { use pretty_assertions::assert_eq; + use crate::app::radarr::DELETE_MOVIE_SELECTION_BLOCKS; + use crate::models::BlockSelectionState; use crate::network::radarr_network::RadarrEvent; use super::*; @@ -173,7 +170,13 @@ mod tests { let mut app = App::default(); app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); app.push_navigation_stack(ActiveRadarrBlock::DeleteMoviePrompt.into()); - app.data.radarr_data.selected_block = ActiveRadarrBlock::DeleteMovieConfirmPrompt; + app.data.radarr_data.selected_block = + BlockSelectionState::new(&DELETE_MOVIE_SELECTION_BLOCKS); + app + .data + .radarr_data + .selected_block + .set_index(DELETE_MOVIE_SELECTION_BLOCKS.len() - 1); app.data.radarr_data.delete_movie_files = true; app.data.radarr_data.add_list_exclusion = true; @@ -200,7 +203,13 @@ mod tests { app.data.radarr_data.prompt_confirm = true; app.data.radarr_data.delete_movie_files = true; app.data.radarr_data.add_list_exclusion = true; - app.data.radarr_data.selected_block = ActiveRadarrBlock::DeleteMovieConfirmPrompt; + app.data.radarr_data.selected_block = + BlockSelectionState::new(&DELETE_MOVIE_SELECTION_BLOCKS); + app + .data + .radarr_data + .selected_block + .set_index(DELETE_MOVIE_SELECTION_BLOCKS.len() - 1); DeleteMovieHandler::with( &SUBMIT_KEY, @@ -225,7 +234,8 @@ mod tests { fn test_delete_movie_toggle_delete_files_submit() { let current_route = ActiveRadarrBlock::DeleteMoviePrompt.into(); let mut app = App::default(); - app.data.radarr_data.selected_block = ActiveRadarrBlock::DeleteMovieToggleDeleteFile; + app.data.radarr_data.selected_block = + BlockSelectionState::new(&DELETE_MOVIE_SELECTION_BLOCKS); app.push_navigation_stack(ActiveRadarrBlock::DeleteMoviePrompt.into()); DeleteMovieHandler::with( diff --git a/src/handlers/radarr_handlers/edit_collection_handler.rs b/src/handlers/radarr_handlers/edit_collection_handler.rs index fd9ce21..1482e0e 100644 --- a/src/handlers/radarr_handlers/edit_collection_handler.rs +++ b/src/handlers/radarr_handlers/edit_collection_handler.rs @@ -7,20 +7,20 @@ use crate::models::Scrollable; use crate::network::radarr_network::RadarrEvent; use crate::{handle_text_box_keys, handle_text_box_left_right_keys}; -pub(super) struct EditCollectionHandler<'a> { +pub(super) struct EditCollectionHandler<'a, 'b> { key: &'a Key, - app: &'a mut App, + app: &'a mut App<'b>, active_radarr_block: &'a ActiveRadarrBlock, context: &'a Option, } -impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for EditCollectionHandler<'a> { +impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditCollectionHandler<'a, 'b> { fn with( key: &'a Key, - app: &'a mut App, + app: &'a mut App<'b>, active_block: &'a ActiveRadarrBlock, context: &'a Option, - ) -> EditCollectionHandler<'a> { + ) -> EditCollectionHandler<'a, 'b> { EditCollectionHandler { key, app, @@ -45,12 +45,7 @@ impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for EditCollectionHandler<'a> { self.app.data.radarr_data.quality_profile_list.scroll_up() } ActiveRadarrBlock::EditCollectionPrompt => { - self.app.data.radarr_data.selected_block = self - .app - .data - .radarr_data - .selected_block - .previous_edit_collection_prompt_block() + self.app.data.radarr_data.selected_block.previous() } _ => (), } @@ -67,14 +62,7 @@ impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for EditCollectionHandler<'a> { ActiveRadarrBlock::EditCollectionSelectQualityProfile => { self.app.data.radarr_data.quality_profile_list.scroll_down() } - ActiveRadarrBlock::EditCollectionPrompt => { - self.app.data.radarr_data.selected_block = self - .app - .data - .radarr_data - .selected_block - .next_edit_collection_prompt_block() - } + ActiveRadarrBlock::EditCollectionPrompt => self.app.data.radarr_data.selected_block.next(), _ => (), } } @@ -135,41 +123,53 @@ impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for EditCollectionHandler<'a> { fn handle_submit(&mut self) { match self.active_radarr_block { - ActiveRadarrBlock::EditCollectionPrompt => match self.app.data.radarr_data.selected_block { - ActiveRadarrBlock::EditCollectionConfirmPrompt => { - if self.app.data.radarr_data.prompt_confirm { - self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::EditCollection); - self.app.should_refresh = true; - } + ActiveRadarrBlock::EditCollectionPrompt => { + match self.app.data.radarr_data.selected_block.get_active_block() { + ActiveRadarrBlock::EditCollectionConfirmPrompt => { + if self.app.data.radarr_data.prompt_confirm { + self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::EditCollection); + self.app.should_refresh = true; + } - self.app.pop_navigation_stack(); + self.app.pop_navigation_stack(); + } + ActiveRadarrBlock::EditCollectionSelectMinimumAvailability + | ActiveRadarrBlock::EditCollectionSelectQualityProfile => { + self.app.push_navigation_stack( + ( + *self.app.data.radarr_data.selected_block.get_active_block(), + *self.context, + ) + .into(), + ) + } + ActiveRadarrBlock::EditCollectionRootFolderPathInput => { + self.app.push_navigation_stack( + ( + *self.app.data.radarr_data.selected_block.get_active_block(), + *self.context, + ) + .into(), + ); + self.app.should_ignore_quit_key = true; + } + ActiveRadarrBlock::EditCollectionToggleMonitored => { + self.app.data.radarr_data.edit_monitored = + Some(!self.app.data.radarr_data.edit_monitored.unwrap_or_default()) + } + ActiveRadarrBlock::EditCollectionToggleSearchOnAdd => { + self.app.data.radarr_data.edit_search_on_add = Some( + !self + .app + .data + .radarr_data + .edit_search_on_add + .unwrap_or_default(), + ) + } + _ => (), } - ActiveRadarrBlock::EditCollectionSelectMinimumAvailability - | ActiveRadarrBlock::EditCollectionSelectQualityProfile => self - .app - .push_navigation_stack((self.app.data.radarr_data.selected_block, *self.context).into()), - ActiveRadarrBlock::EditCollectionRootFolderPathInput => { - self.app.push_navigation_stack( - (self.app.data.radarr_data.selected_block, *self.context).into(), - ); - self.app.should_ignore_quit_key = true; - } - ActiveRadarrBlock::EditCollectionToggleMonitored => { - self.app.data.radarr_data.edit_monitored = - Some(!self.app.data.radarr_data.edit_monitored.unwrap_or_default()) - } - ActiveRadarrBlock::EditCollectionToggleSearchOnAdd => { - self.app.data.radarr_data.edit_search_on_add = Some( - !self - .app - .data - .radarr_data - .edit_search_on_add - .unwrap_or_default(), - ) - } - _ => (), - }, + } ActiveRadarrBlock::EditCollectionSelectMinimumAvailability | ActiveRadarrBlock::EditCollectionSelectQualityProfile => self.app.pop_navigation_stack(), ActiveRadarrBlock::EditCollectionRootFolderPathInput => { @@ -222,6 +222,8 @@ mod tests { use rstest::rstest; use strum::IntoEnumIterator; + use crate::app::radarr::EDIT_COLLECTION_SELECTION_BLOCKS; + use crate::models::BlockSelectionState; use crate::{test_enum_scroll, test_iterable_scroll}; use super::*; @@ -247,7 +249,8 @@ mod tests { fn test_edit_collection_prompt_scroll(#[values(Key::Up, Key::Down)] key: Key) { let mut app = App::default(); app.data.radarr_data.selected_block = - ActiveRadarrBlock::EditCollectionSelectMinimumAvailability; + BlockSelectionState::new(&EDIT_COLLECTION_SELECTION_BLOCKS); + app.data.radarr_data.selected_block.next(); EditCollectionHandler::with( &key, @@ -259,13 +262,13 @@ mod tests { if key == Key::Up { assert_eq!( - app.data.radarr_data.selected_block, - ActiveRadarrBlock::EditCollectionToggleMonitored + app.data.radarr_data.selected_block.get_active_block(), + &ActiveRadarrBlock::EditCollectionToggleMonitored ); } else { assert_eq!( - app.data.radarr_data.selected_block, - ActiveRadarrBlock::EditCollectionSelectQualityProfile + app.data.radarr_data.selected_block.get_active_block(), + &ActiveRadarrBlock::EditCollectionSelectQualityProfile ); } } @@ -351,7 +354,8 @@ mod tests { use pretty_assertions::assert_eq; use rstest::rstest; - use crate::models::Route; + use crate::app::radarr::EDIT_COLLECTION_SELECTION_BLOCKS; + use crate::models::{BlockSelectionState, Route}; use crate::network::radarr_network::RadarrEvent; use super::*; @@ -387,7 +391,13 @@ mod tests { let mut app = App::default(); app.push_navigation_stack(ActiveRadarrBlock::Collections.into()); app.push_navigation_stack(ActiveRadarrBlock::EditCollectionPrompt.into()); - app.data.radarr_data.selected_block = ActiveRadarrBlock::EditCollectionConfirmPrompt; + app.data.radarr_data.selected_block = + BlockSelectionState::new(&EDIT_COLLECTION_SELECTION_BLOCKS); + app + .data + .radarr_data + .selected_block + .set_index(EDIT_COLLECTION_SELECTION_BLOCKS.len() - 1); EditCollectionHandler::with( &SUBMIT_KEY, @@ -410,7 +420,13 @@ mod tests { app.push_navigation_stack(ActiveRadarrBlock::Collections.into()); app.push_navigation_stack(ActiveRadarrBlock::EditCollectionPrompt.into()); app.data.radarr_data.prompt_confirm = true; - app.data.radarr_data.selected_block = ActiveRadarrBlock::EditCollectionConfirmPrompt; + app.data.radarr_data.selected_block = + BlockSelectionState::new(&EDIT_COLLECTION_SELECTION_BLOCKS); + app + .data + .radarr_data + .selected_block + .set_index(EDIT_COLLECTION_SELECTION_BLOCKS.len() - 1); EditCollectionHandler::with( &SUBMIT_KEY, @@ -438,7 +454,8 @@ mod tests { Some(ActiveRadarrBlock::Collections), )); let mut app = App::default(); - app.data.radarr_data.selected_block = ActiveRadarrBlock::EditCollectionToggleMonitored; + app.data.radarr_data.selected_block = + BlockSelectionState::new(&EDIT_COLLECTION_SELECTION_BLOCKS); app.push_navigation_stack(current_route); EditCollectionHandler::with( @@ -471,7 +488,13 @@ mod tests { Some(ActiveRadarrBlock::Collections), )); let mut app = App::default(); - app.data.radarr_data.selected_block = ActiveRadarrBlock::EditCollectionToggleSearchOnAdd; + app.data.radarr_data.selected_block = + BlockSelectionState::new(&EDIT_COLLECTION_SELECTION_BLOCKS); + app + .data + .radarr_data + .selected_block + .set_index(EDIT_COLLECTION_SELECTION_BLOCKS.len() - 2); app.push_navigation_stack(current_route); EditCollectionHandler::with( @@ -498,13 +521,12 @@ mod tests { } #[rstest] + #[case(ActiveRadarrBlock::EditCollectionSelectMinimumAvailability, 1)] + #[case(ActiveRadarrBlock::EditCollectionSelectQualityProfile, 2)] + #[case(ActiveRadarrBlock::EditCollectionRootFolderPathInput, 3)] fn test_edit_collection_prompt_selected_block_submit( - #[values( - ActiveRadarrBlock::EditCollectionSelectMinimumAvailability, - ActiveRadarrBlock::EditCollectionSelectQualityProfile, - ActiveRadarrBlock::EditCollectionRootFolderPathInput - )] - selected_block: ActiveRadarrBlock, + #[case] selected_block: ActiveRadarrBlock, + #[case] index: usize, ) { let mut app = App::default(); app.push_navigation_stack( @@ -514,7 +536,9 @@ mod tests { ) .into(), ); - app.data.radarr_data.selected_block = selected_block; + app.data.radarr_data.selected_block = + BlockSelectionState::new(&EDIT_COLLECTION_SELECTION_BLOCKS); + app.data.radarr_data.selected_block.set_index(index); EditCollectionHandler::with( &SUBMIT_KEY, diff --git a/src/handlers/radarr_handlers/edit_movie_handler.rs b/src/handlers/radarr_handlers/edit_movie_handler.rs index 0ac5a2c..cecfdbe 100644 --- a/src/handlers/radarr_handlers/edit_movie_handler.rs +++ b/src/handlers/radarr_handlers/edit_movie_handler.rs @@ -7,20 +7,20 @@ use crate::models::Scrollable; use crate::network::radarr_network::RadarrEvent; use crate::{handle_text_box_keys, handle_text_box_left_right_keys}; -pub(super) struct EditMovieHandler<'a> { +pub(super) struct EditMovieHandler<'a, 'b> { key: &'a Key, - app: &'a mut App, + app: &'a mut App<'b>, active_radarr_block: &'a ActiveRadarrBlock, context: &'a Option, } -impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for EditMovieHandler<'a> { +impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for EditMovieHandler<'a, 'b> { fn with( key: &'a Key, - app: &'a mut App, + app: &'a mut App<'b>, active_block: &'a ActiveRadarrBlock, context: &'a Option, - ) -> EditMovieHandler<'a> { + ) -> EditMovieHandler<'a, 'b> { EditMovieHandler { key, app, @@ -44,14 +44,7 @@ impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for EditMovieHandler<'a> { ActiveRadarrBlock::EditMovieSelectQualityProfile => { self.app.data.radarr_data.quality_profile_list.scroll_up() } - ActiveRadarrBlock::EditMoviePrompt => { - self.app.data.radarr_data.selected_block = self - .app - .data - .radarr_data - .selected_block - .previous_edit_movie_prompt_block() - } + ActiveRadarrBlock::EditMoviePrompt => self.app.data.radarr_data.selected_block.previous(), _ => (), } } @@ -67,14 +60,7 @@ impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for EditMovieHandler<'a> { ActiveRadarrBlock::EditMovieSelectQualityProfile => { self.app.data.radarr_data.quality_profile_list.scroll_down() } - ActiveRadarrBlock::EditMoviePrompt => { - self.app.data.radarr_data.selected_block = self - .app - .data - .radarr_data - .selected_block - .next_edit_movie_prompt_block() - } + ActiveRadarrBlock::EditMoviePrompt => self.app.data.radarr_data.selected_block.next(), _ => (), } } @@ -136,31 +122,41 @@ impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for EditMovieHandler<'a> { fn handle_submit(&mut self) { match self.active_radarr_block { - ActiveRadarrBlock::EditMoviePrompt => match self.app.data.radarr_data.selected_block { - ActiveRadarrBlock::EditMovieConfirmPrompt => { - if self.app.data.radarr_data.prompt_confirm { - self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::EditMovie); - self.app.should_refresh = true; - } + ActiveRadarrBlock::EditMoviePrompt => { + match self.app.data.radarr_data.selected_block.get_active_block() { + ActiveRadarrBlock::EditMovieConfirmPrompt => { + if self.app.data.radarr_data.prompt_confirm { + self.app.data.radarr_data.prompt_confirm_action = Some(RadarrEvent::EditMovie); + self.app.should_refresh = true; + } - self.app.pop_navigation_stack(); + self.app.pop_navigation_stack(); + } + ActiveRadarrBlock::EditMovieSelectMinimumAvailability + | ActiveRadarrBlock::EditMovieSelectQualityProfile => self.app.push_navigation_stack( + ( + *self.app.data.radarr_data.selected_block.get_active_block(), + *self.context, + ) + .into(), + ), + ActiveRadarrBlock::EditMoviePathInput | ActiveRadarrBlock::EditMovieTagsInput => { + self.app.push_navigation_stack( + ( + *self.app.data.radarr_data.selected_block.get_active_block(), + *self.context, + ) + .into(), + ); + self.app.should_ignore_quit_key = true; + } + ActiveRadarrBlock::EditMovieToggleMonitored => { + self.app.data.radarr_data.edit_monitored = + Some(!self.app.data.radarr_data.edit_monitored.unwrap_or_default()) + } + _ => (), } - ActiveRadarrBlock::EditMovieSelectMinimumAvailability - | ActiveRadarrBlock::EditMovieSelectQualityProfile => self - .app - .push_navigation_stack((self.app.data.radarr_data.selected_block, *self.context).into()), - ActiveRadarrBlock::EditMoviePathInput | ActiveRadarrBlock::EditMovieTagsInput => { - self.app.push_navigation_stack( - (self.app.data.radarr_data.selected_block, *self.context).into(), - ); - self.app.should_ignore_quit_key = true; - } - ActiveRadarrBlock::EditMovieToggleMonitored => { - self.app.data.radarr_data.edit_monitored = - Some(!self.app.data.radarr_data.edit_monitored.unwrap_or_default()) - } - _ => (), - }, + } ActiveRadarrBlock::EditMovieSelectMinimumAvailability | ActiveRadarrBlock::EditMovieSelectQualityProfile => self.app.pop_navigation_stack(), ActiveRadarrBlock::EditMoviePathInput | ActiveRadarrBlock::EditMovieTagsInput => { @@ -219,6 +215,8 @@ mod tests { use rstest::rstest; use strum::IntoEnumIterator; + use crate::app::radarr::EDIT_MOVIE_SELECTION_BLOCKS; + use crate::models::BlockSelectionState; use crate::{test_enum_scroll, test_iterable_scroll}; use super::*; @@ -243,19 +241,20 @@ mod tests { #[rstest] fn test_edit_movie_prompt_scroll(#[values(Key::Up, Key::Down)] key: Key) { let mut app = App::default(); - app.data.radarr_data.selected_block = ActiveRadarrBlock::EditMovieSelectMinimumAvailability; + app.data.radarr_data.selected_block = BlockSelectionState::new(&EDIT_MOVIE_SELECTION_BLOCKS); + app.data.radarr_data.selected_block.next(); EditMovieHandler::with(&key, &mut app, &ActiveRadarrBlock::EditMoviePrompt, &None).handle(); if key == Key::Up { assert_eq!( - app.data.radarr_data.selected_block, - ActiveRadarrBlock::EditMovieToggleMonitored + app.data.radarr_data.selected_block.get_active_block(), + &ActiveRadarrBlock::EditMovieToggleMonitored ); } else { assert_eq!( - app.data.radarr_data.selected_block, - ActiveRadarrBlock::EditMovieSelectQualityProfile + app.data.radarr_data.selected_block.get_active_block(), + &ActiveRadarrBlock::EditMovieSelectQualityProfile ); } } @@ -347,7 +346,8 @@ mod tests { use pretty_assertions::assert_eq; use rstest::rstest; - use crate::models::Route; + use crate::app::radarr::{EDIT_COLLECTION_SELECTION_BLOCKS, EDIT_MOVIE_SELECTION_BLOCKS}; + use crate::models::{BlockSelectionState, Route}; use crate::network::radarr_network::RadarrEvent; use super::*; @@ -407,7 +407,12 @@ mod tests { let mut app = App::default(); app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); app.push_navigation_stack(ActiveRadarrBlock::EditMoviePrompt.into()); - app.data.radarr_data.selected_block = ActiveRadarrBlock::EditMovieConfirmPrompt; + app.data.radarr_data.selected_block = BlockSelectionState::new(&EDIT_MOVIE_SELECTION_BLOCKS); + app + .data + .radarr_data + .selected_block + .set_index(EDIT_COLLECTION_SELECTION_BLOCKS.len() - 1); EditMovieHandler::with( &SUBMIT_KEY, @@ -427,7 +432,12 @@ mod tests { app.push_navigation_stack(ActiveRadarrBlock::Movies.into()); app.push_navigation_stack(ActiveRadarrBlock::EditMoviePrompt.into()); app.data.radarr_data.prompt_confirm = true; - app.data.radarr_data.selected_block = ActiveRadarrBlock::EditMovieConfirmPrompt; + app.data.radarr_data.selected_block = BlockSelectionState::new(&EDIT_MOVIE_SELECTION_BLOCKS); + app + .data + .radarr_data + .selected_block + .set_index(EDIT_COLLECTION_SELECTION_BLOCKS.len() - 1); EditMovieHandler::with( &SUBMIT_KEY, @@ -452,7 +462,7 @@ mod tests { Some(ActiveRadarrBlock::Movies), )); let mut app = App::default(); - app.data.radarr_data.selected_block = ActiveRadarrBlock::EditMovieToggleMonitored; + app.data.radarr_data.selected_block = BlockSelectionState::new(&EDIT_MOVIE_SELECTION_BLOCKS); app.push_navigation_stack(current_route); EditMovieHandler::with( @@ -479,14 +489,13 @@ mod tests { } #[rstest] + #[case(ActiveRadarrBlock::EditMovieSelectMinimumAvailability, 1)] + #[case(ActiveRadarrBlock::EditMovieSelectQualityProfile, 2)] + #[case(ActiveRadarrBlock::EditMoviePathInput, 3)] + #[case(ActiveRadarrBlock::EditMovieTagsInput, 4)] fn test_edit_movie_prompt_selected_block_submit( - #[values( - ActiveRadarrBlock::EditMovieSelectMinimumAvailability, - ActiveRadarrBlock::EditMovieSelectQualityProfile, - ActiveRadarrBlock::EditMoviePathInput, - ActiveRadarrBlock::EditMovieTagsInput - )] - selected_block: ActiveRadarrBlock, + #[case] selected_block: ActiveRadarrBlock, + #[case] index: usize, ) { let mut app = App::default(); app.push_navigation_stack( @@ -496,7 +505,8 @@ mod tests { ) .into(), ); - app.data.radarr_data.selected_block = selected_block; + app.data.radarr_data.selected_block = BlockSelectionState::new(&EDIT_MOVIE_SELECTION_BLOCKS); + app.data.radarr_data.selected_block.set_index(index); EditMovieHandler::with( &SUBMIT_KEY, diff --git a/src/handlers/radarr_handlers/mod.rs b/src/handlers/radarr_handlers/mod.rs index 6966dc2..fb0a833 100644 --- a/src/handlers/radarr_handlers/mod.rs +++ b/src/handlers/radarr_handlers/mod.rs @@ -1,7 +1,9 @@ use crate::app::key_binding::DEFAULT_KEYBINDINGS; use crate::app::radarr::{ ActiveRadarrBlock, ADD_MOVIE_BLOCKS, COLLECTION_DETAILS_BLOCKS, DELETE_MOVIE_BLOCKS, - EDIT_COLLECTION_BLOCKS, EDIT_MOVIE_BLOCKS, FILTER_BLOCKS, MOVIE_DETAILS_BLOCKS, SEARCH_BLOCKS, + DELETE_MOVIE_SELECTION_BLOCKS, EDIT_COLLECTION_BLOCKS, EDIT_COLLECTION_SELECTION_BLOCKS, + EDIT_MOVIE_BLOCKS, EDIT_MOVIE_SELECTION_BLOCKS, FILTER_BLOCKS, MOVIE_DETAILS_BLOCKS, + SEARCH_BLOCKS, }; use crate::handlers::radarr_handlers::add_movie_handler::AddMovieHandler; use crate::handlers::radarr_handlers::collection_details_handler::CollectionDetailsHandler; @@ -10,7 +12,7 @@ use crate::handlers::radarr_handlers::edit_collection_handler::EditCollectionHan use crate::handlers::radarr_handlers::edit_movie_handler::EditMovieHandler; use crate::handlers::radarr_handlers::movie_details_handler::MovieDetailsHandler; use crate::handlers::{handle_clear_errors, handle_prompt_toggle, KeyEventHandler}; -use crate::models::{HorizontallyScrollableText, Scrollable}; +use crate::models::{BlockSelectionState, HorizontallyScrollableText, Scrollable}; use crate::network::radarr_network::RadarrEvent; use crate::utils::strip_non_search_characters; use crate::{handle_text_box_keys, handle_text_box_left_right_keys, App, Key}; @@ -22,14 +24,14 @@ mod edit_collection_handler; mod edit_movie_handler; mod movie_details_handler; -pub(super) struct RadarrHandler<'a> { +pub(super) struct RadarrHandler<'a, 'b> { key: &'a Key, - app: &'a mut App, + app: &'a mut App<'b>, active_radarr_block: &'a ActiveRadarrBlock, context: &'a Option, } -impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for RadarrHandler<'a> { +impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for RadarrHandler<'a, 'b> { fn handle(&mut self) { match self.active_radarr_block { _ if MOVIE_DETAILS_BLOCKS.contains(self.active_radarr_block) => { @@ -60,10 +62,10 @@ impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for RadarrHandler<'a> { fn with( key: &'a Key, - app: &'a mut App, + app: &'a mut App<'b>, active_block: &'a ActiveRadarrBlock, context: &'a Option, - ) -> RadarrHandler<'a> { + ) -> RadarrHandler<'a, 'b> { RadarrHandler { key, app, @@ -222,7 +224,8 @@ impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for RadarrHandler<'a> { self .app .push_navigation_stack(ActiveRadarrBlock::DeleteMoviePrompt.into()); - self.app.data.radarr_data.selected_block = ActiveRadarrBlock::DeleteMovieToggleDeleteFile; + self.app.data.radarr_data.selected_block = + BlockSelectionState::new(&DELETE_MOVIE_SELECTION_BLOCKS); } ActiveRadarrBlock::Downloads => self .app @@ -239,7 +242,8 @@ impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for RadarrHandler<'a> { ActiveRadarrBlock::Movies | ActiveRadarrBlock::Downloads | ActiveRadarrBlock::Collections - | ActiveRadarrBlock::RootFolders => match self.key { + | ActiveRadarrBlock::RootFolders + | ActiveRadarrBlock::System => match self.key { _ if *self.key == DEFAULT_KEYBINDINGS.left.key => { self.app.data.radarr_data.main_tabs.previous(); self @@ -473,7 +477,8 @@ impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for RadarrHandler<'a> { .into(), ); self.app.data.radarr_data.populate_edit_movie_fields(); - self.app.data.radarr_data.selected_block = ActiveRadarrBlock::EditMovieToggleMonitored; + self.app.data.radarr_data.selected_block = + BlockSelectionState::new(&EDIT_MOVIE_SELECTION_BLOCKS); } _ if *key == DEFAULT_KEYBINDINGS.add.key => { self @@ -527,7 +532,7 @@ impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for RadarrHandler<'a> { ); self.app.data.radarr_data.populate_edit_collection_fields(); self.app.data.radarr_data.selected_block = - ActiveRadarrBlock::EditCollectionToggleMonitored; + BlockSelectionState::new(&EDIT_COLLECTION_SELECTION_BLOCKS); } _ if *key == DEFAULT_KEYBINDINGS.update.key => { self @@ -565,7 +570,7 @@ impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for RadarrHandler<'a> { } } -impl RadarrHandler<'_> { +impl<'a, 'b> RadarrHandler<'a, 'b> { fn search_table(&mut self, rows: &[T], field_selection_fn: F) -> Option where F: Fn(&T) -> &str, @@ -636,6 +641,7 @@ mod radarr_handler_test_utils { tags: vec![Number::from(1)], ..Movie::default() }]); + radarr_data.selected_block = BlockSelectionState::new(&EDIT_MOVIE_SELECTION_BLOCKS); app.data.radarr_data = radarr_data; $handler::with(&DEFAULT_KEYBINDINGS.edit.key, &mut app, &$block, &None).handle(); @@ -645,8 +651,8 @@ mod radarr_handler_test_utils { &(ActiveRadarrBlock::EditMoviePrompt, Some($context)).into() ); assert_eq!( - app.data.radarr_data.selected_block, - ActiveRadarrBlock::EditMovieToggleMonitored + app.data.radarr_data.selected_block.get_active_block(), + &ActiveRadarrBlock::EditMovieToggleMonitored ); assert_eq!( app.data.radarr_data.minimum_availability_list.items, @@ -702,6 +708,7 @@ mod radarr_handler_test_utils { minimum_availability: MinimumAvailability::Released, ..Collection::default() }]); + radarr_data.selected_block = BlockSelectionState::new(&EDIT_COLLECTION_SELECTION_BLOCKS); app.data.radarr_data = radarr_data; $handler::with(&DEFAULT_KEYBINDINGS.edit.key, &mut app, &$block, &None).handle(); @@ -711,8 +718,8 @@ mod radarr_handler_test_utils { &(ActiveRadarrBlock::EditCollectionPrompt, Some($context)).into() ); assert_eq!( - app.data.radarr_data.selected_block, - ActiveRadarrBlock::EditCollectionToggleMonitored + app.data.radarr_data.selected_block.get_active_block(), + &ActiveRadarrBlock::EditCollectionToggleMonitored ); assert_eq!( app.data.radarr_data.minimum_availability_list.items, @@ -951,8 +958,8 @@ mod tests { &ActiveRadarrBlock::DeleteMoviePrompt.into() ); assert_eq!( - app.data.radarr_data.selected_block, - ActiveRadarrBlock::DeleteMovieToggleDeleteFile + app.data.radarr_data.selected_block.get_active_block(), + &ActiveRadarrBlock::DeleteMovieToggleDeleteFile ); } @@ -996,10 +1003,11 @@ mod tests { use super::*; #[rstest] - #[case(ActiveRadarrBlock::Movies, 0, ActiveRadarrBlock::RootFolders)] - #[case(ActiveRadarrBlock::Downloads, 1, ActiveRadarrBlock::Movies)] - #[case(ActiveRadarrBlock::Collections, 2, ActiveRadarrBlock::Downloads)] + #[case(ActiveRadarrBlock::Movies, 0, ActiveRadarrBlock::System)] + #[case(ActiveRadarrBlock::System, 4, ActiveRadarrBlock::RootFolders)] #[case(ActiveRadarrBlock::RootFolders, 3, ActiveRadarrBlock::Collections)] + #[case(ActiveRadarrBlock::Collections, 2, ActiveRadarrBlock::Downloads)] + #[case(ActiveRadarrBlock::Downloads, 1, ActiveRadarrBlock::Movies)] fn test_radarr_tab_left( #[case] active_radarr_block: ActiveRadarrBlock, #[case] index: usize, @@ -1027,7 +1035,8 @@ mod tests { #[case(ActiveRadarrBlock::Movies, 0, ActiveRadarrBlock::Downloads)] #[case(ActiveRadarrBlock::Downloads, 1, ActiveRadarrBlock::Collections)] #[case(ActiveRadarrBlock::Collections, 2, ActiveRadarrBlock::RootFolders)] - #[case(ActiveRadarrBlock::RootFolders, 3, ActiveRadarrBlock::Movies)] + #[case(ActiveRadarrBlock::RootFolders, 3, ActiveRadarrBlock::System)] + #[case(ActiveRadarrBlock::System, 4, ActiveRadarrBlock::Movies)] fn test_radarr_tab_right( #[case] active_radarr_block: ActiveRadarrBlock, #[case] index: usize, @@ -1549,7 +1558,10 @@ mod tests { use crate::app::radarr::radarr_test_utils::create_test_radarr_data; use crate::app::radarr::RadarrData; + use crate::app::radarr::EDIT_COLLECTION_SELECTION_BLOCKS; + use crate::app::radarr::EDIT_MOVIE_SELECTION_BLOCKS; use crate::models::radarr_models::MinimumAvailability; + use crate::models::BlockSelectionState; use crate::models::HorizontallyScrollableText; use crate::models::StatefulTable; diff --git a/src/handlers/radarr_handlers/movie_details_handler.rs b/src/handlers/radarr_handlers/movie_details_handler.rs index 68bd0c9..9832efb 100644 --- a/src/handlers/radarr_handlers/movie_details_handler.rs +++ b/src/handlers/radarr_handlers/movie_details_handler.rs @@ -4,28 +4,28 @@ use serde_json::Number; use strum::IntoEnumIterator; use crate::app::key_binding::DEFAULT_KEYBINDINGS; -use crate::app::radarr::ActiveRadarrBlock; +use crate::app::radarr::{ActiveRadarrBlock, EDIT_MOVIE_SELECTION_BLOCKS}; use crate::app::App; use crate::event::Key; use crate::handlers::{handle_prompt_toggle, KeyEventHandler}; use crate::models::radarr_models::{Language, Release, ReleaseField}; -use crate::models::Scrollable; +use crate::models::{BlockSelectionState, Scrollable}; use crate::network::radarr_network::RadarrEvent; -pub(super) struct MovieDetailsHandler<'a> { +pub(super) struct MovieDetailsHandler<'a, 'b> { key: &'a Key, - app: &'a mut App, + app: &'a mut App<'b>, active_radarr_block: &'a ActiveRadarrBlock, _context: &'a Option, } -impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for MovieDetailsHandler<'a> { +impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for MovieDetailsHandler<'a, 'b> { fn with( key: &'a Key, - app: &'a mut App, + app: &'a mut App<'b>, active_block: &'a ActiveRadarrBlock, _context: &'a Option, - ) -> MovieDetailsHandler<'a> { + ) -> MovieDetailsHandler<'a, 'b> { MovieDetailsHandler { key, app, @@ -234,7 +234,8 @@ impl<'a> KeyEventHandler<'a, ActiveRadarrBlock> for MovieDetailsHandler<'a> { .into(), ); self.app.data.radarr_data.populate_edit_movie_fields(); - self.app.data.radarr_data.selected_block = ActiveRadarrBlock::EditMovieToggleMonitored; + self.app.data.radarr_data.selected_block = + BlockSelectionState::new(&EDIT_MOVIE_SELECTION_BLOCKS); } _ if *key == DEFAULT_KEYBINDINGS.update.key => { self @@ -779,7 +780,9 @@ mod tests { use crate::app::radarr::radarr_test_utils::create_test_radarr_data; use crate::app::radarr::RadarrData; + use crate::app::radarr::EDIT_MOVIE_SELECTION_BLOCKS; use crate::models::radarr_models::{MinimumAvailability, Movie}; + use crate::models::BlockSelectionState; use crate::models::HorizontallyScrollableText; use crate::models::StatefulTable; use crate::test_edit_movie_key; diff --git a/src/main.rs b/src/main.rs index 8e2b46c..c3fab5e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -57,7 +57,7 @@ async fn main() -> Result<()> { } #[tokio::main] -async fn start_networking(mut network_rx: Receiver, app: &Arc>) { +async fn start_networking(mut network_rx: Receiver, app: &Arc>>) { let network = Network::new(reqwest::Client::new(), app); while let Some(network_event) = network_rx.recv().await { @@ -65,7 +65,7 @@ async fn start_networking(mut network_rx: Receiver, app: &Arc>) -> Result<()> { +async fn start_ui(app: &Arc>>) -> Result<()> { let mut stdout = io::stdout(); enable_raw_mode()?; diff --git a/src/models/mod.rs b/src/models/mod.rs index 45a17c1..21a1502 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -250,12 +250,12 @@ impl HorizontallyScrollableText { } } -#[derive(Clone, PartialEq, Eq, Debug)] +#[derive(Clone, Copy, PartialEq, Eq, Debug)] pub struct TabRoute { - pub title: String, + pub title: &'static str, pub route: Route, - pub help: String, - pub contextual_help: Option, + pub help: &'static str, + pub contextual_help: Option<&'static str>, } pub struct TabState { @@ -279,12 +279,12 @@ impl TabState { &self.tabs[self.index].route } - pub fn get_active_tab_help(&self) -> String { - self.tabs[self.index].help.clone() + pub fn get_active_tab_help(&self) -> &'static str { + self.tabs[self.index].help } - pub fn get_active_tab_contextual_help(&self) -> Option { - self.tabs[self.index].contextual_help.clone() + pub fn get_active_tab_contextual_help(&self) -> Option<&'static str> { + self.tabs[self.index].contextual_help } pub fn next(&mut self) { @@ -300,6 +300,50 @@ impl TabState { } } +#[derive(Default, Eq, PartialEq, Debug)] +pub struct BlockSelectionState<'a, T> +where + T: Sized + Clone + Copy + Default, +{ + pub blocks: &'a [T], + pub index: usize, +} + +impl<'a, T> BlockSelectionState<'a, T> +where + T: Sized + Clone + Copy + Default, +{ + pub fn new(blocks: &'a [T]) -> BlockSelectionState<'a, T> { + BlockSelectionState { blocks, index: 0 } + } + + pub fn get_active_block(&self) -> &T { + &self.blocks[self.index] + } + + pub fn next(&mut self) { + self.index = (self.index + 1) % self.blocks.len(); + } + + pub fn previous(&mut self) { + if self.index > 0 { + self.index -= 1; + } else { + self.index = self.blocks.len() - 1; + } + } +} + +#[cfg(test)] +impl<'a, T> BlockSelectionState<'a, T> +where + T: Sized + Clone + Copy + Default, +{ + pub fn set_index(&mut self, index: usize) { + self.index = index; + } +} + #[cfg(test)] mod tests { use std::cell::RefCell; @@ -308,9 +352,19 @@ mod tests { use crate::app::radarr::ActiveRadarrBlock; use crate::models::{ - HorizontallyScrollableText, Scrollable, ScrollableText, StatefulTable, TabRoute, TabState, + BlockSelectionState, HorizontallyScrollableText, Scrollable, ScrollableText, StatefulTable, + TabRoute, TabState, }; + const BLOCKS: [ActiveRadarrBlock; 6] = [ + ActiveRadarrBlock::AddMovieSelectRootFolder, + ActiveRadarrBlock::AddMovieSelectMonitor, + ActiveRadarrBlock::AddMovieSelectMinimumAvailability, + ActiveRadarrBlock::AddMovieSelectQualityProfile, + ActiveRadarrBlock::AddMovieTagsInput, + ActiveRadarrBlock::AddMovieConfirmPrompt, + ]; + #[test] fn test_stateful_table_scroll() { let mut stateful_table = create_test_stateful_table(); @@ -672,11 +726,9 @@ mod tests { #[test] fn test_tab_state_get_active_route() { - let second_tab = create_test_tab_routes()[1].clone().route; - let tab_state = TabState { - tabs: create_test_tab_routes(), - index: 1, - }; + let tabs = create_test_tab_routes(); + let second_tab = tabs[1].route; + let tab_state = TabState { tabs, index: 1 }; let active_route = tab_state.get_active_route(); @@ -685,11 +737,9 @@ mod tests { #[test] fn test_tab_state_get_active_tab_help() { - let second_tab_help = create_test_tab_routes()[1].clone().help; - let tab_state = TabState { - tabs: create_test_tab_routes(), - index: 1, - }; + let tabs = create_test_tab_routes(); + let second_tab_help = tabs[1].help; + let tab_state = TabState { tabs, index: 1 }; let tab_help = tab_state.get_active_tab_help(); @@ -698,11 +748,9 @@ mod tests { #[test] fn test_tab_state_get_active_tab_contextual_help() { - let second_tab_contextual_help = create_test_tab_routes()[1].clone().contextual_help.unwrap(); - let tab_state = TabState { - tabs: create_test_tab_routes(), - index: 1, - }; + let tabs = create_test_tab_routes(); + let second_tab_contextual_help = tabs[1].contextual_help.unwrap(); + let tab_state = TabState { tabs, index: 1 }; let tab_contextual_help = tab_state.get_active_tab_contextual_help(); @@ -742,19 +790,77 @@ mod tests { assert_eq!(tab_state.get_active_route(), &tab_routes[0].route); } + #[test] + fn test_block_selection_state_new() { + let block_selection_state = BlockSelectionState::new(&BLOCKS); + + assert_eq!(block_selection_state.index, 0); + } + + #[test] + fn test_block_selection_state_get_active_block() { + let second_block = BLOCKS[1]; + let block_selection_state = BlockSelectionState { + blocks: &BLOCKS, + index: 1, + }; + + let active_block = block_selection_state.get_active_block(); + + assert_eq!(active_block, &second_block); + } + + #[test] + fn test_block_selection_state_next() { + let blocks = [ + ActiveRadarrBlock::AddMovieSelectRootFolder, + ActiveRadarrBlock::AddMovieSelectMonitor, + ]; + let mut block_selection_state = BlockSelectionState::new(&blocks); + + assert_eq!(block_selection_state.get_active_block(), &blocks[0]); + + block_selection_state.next(); + + assert_eq!(block_selection_state.get_active_block(), &blocks[1]); + + block_selection_state.next(); + + assert_eq!(block_selection_state.get_active_block(), &blocks[0]); + } + + #[test] + fn test_block_selection_state_previous() { + let blocks = [ + ActiveRadarrBlock::AddMovieSelectRootFolder, + ActiveRadarrBlock::AddMovieSelectMonitor, + ]; + let mut block_selection_state = BlockSelectionState::new(&blocks); + + assert_eq!(block_selection_state.get_active_block(), &blocks[0]); + + block_selection_state.previous(); + + assert_eq!(block_selection_state.get_active_block(), &blocks[1]); + + block_selection_state.previous(); + + assert_eq!(block_selection_state.get_active_block(), &blocks[0]); + } + fn create_test_tab_routes() -> Vec { vec![ TabRoute { - title: "Test 1".to_owned(), + title: "Test 1", route: ActiveRadarrBlock::Movies.into(), - help: "Help for Test 1".to_owned(), - contextual_help: Some("Contextual Help for Test 1".to_owned()), + help: "Help for Test 1", + contextual_help: Some("Contextual Help for Test 1"), }, TabRoute { - title: "Test 2".to_owned(), + title: "Test 2", route: ActiveRadarrBlock::Collections.into(), - help: "Help for Test 2".to_owned(), - contextual_help: Some("Contextual Help for Test 2".to_owned()), + help: "Help for Test 2", + contextual_help: Some("Contextual Help for Test 2"), }, ] } diff --git a/src/network/mod.rs b/src/network/mod.rs index c0f7cb8..aa66190 100644 --- a/src/network/mod.rs +++ b/src/network/mod.rs @@ -21,13 +21,13 @@ pub enum NetworkEvent { Radarr(RadarrEvent), } -pub struct Network<'a> { +pub struct Network<'a, 'b> { pub client: Client, - pub app: &'a Arc>, + pub app: &'a Arc>>, } -impl<'a> Network<'a> { - pub fn new(client: Client, app: &'a Arc>) -> Self { +impl<'a, 'b> Network<'a, 'b> { + pub fn new(client: Client, app: &'a Arc>>) -> Self { Network { client, app } } @@ -43,7 +43,7 @@ impl<'a> Network<'a> { pub async fn handle_request( &self, request_props: RequestProps, - mut app_update_fn: impl FnMut(R, MutexGuard<'_, App>), + mut app_update_fn: impl FnMut(R, MutexGuard<'_, App<'_>>), ) where B: Serialize + Default + Debug, R: DeserializeOwned, @@ -427,11 +427,11 @@ mod tests { pub value: String, } - async fn mock_api( + async fn mock_api<'a>( method: RequestMethod, response_status: usize, has_response_body: bool, - ) -> (Mock, Arc>, ServerGuard) { + ) -> (Mock, Arc>>, ServerGuard) { let mut server = Server::new_async().await; let mut async_server = server .mock(&method.to_string().to_uppercase(), "/test") diff --git a/src/network/radarr_network.rs b/src/network/radarr_network.rs index 4280d03..64cd347 100644 --- a/src/network/radarr_network.rs +++ b/src/network/radarr_network.rs @@ -86,7 +86,7 @@ impl From for NetworkEvent { } } -impl<'a> Network<'a> { +impl<'a, 'b> Network<'a, 'b> { pub async fn handle_radarr_event(&self, radarr_event: RadarrEvent) { match radarr_event { RadarrEvent::AddMovie => self.add_movie().await, @@ -1533,13 +1533,9 @@ mod test { "languages": [ { "name": "English" } ], "quality": { "quality": { "name": "HD - 1080p" }} }]); - let (async_server, app_arc, _server) = mock_radarr_api( - RequestMethod::Get, - None, - Some(release_json), - format!("{}?movieId=1", RadarrEvent::GetReleases.resource()).as_str(), - ) - .await; + let resource = format!("{}?movieId=1", RadarrEvent::GetReleases.resource()); + let (async_server, app_arc, _server) = + mock_radarr_api(RequestMethod::Get, None, Some(release_json), &resource).await; app_arc .lock() .await @@ -1581,15 +1577,15 @@ mod test { } } }]); + let resource = format!( + "{}?term=test%20term", + RadarrEvent::SearchNewMovie.resource() + ); let (async_server, app_arc, _server) = mock_radarr_api( RequestMethod::Get, None, Some(add_movie_search_result_json), - format!( - "{}?term=test%20term", - RadarrEvent::SearchNewMovie.resource() - ) - .as_str(), + &resource, ) .await; app_arc.lock().await.data.radarr_data.search = "test term".to_owned().into(); @@ -1614,17 +1610,12 @@ mod test { #[tokio::test] async fn test_handle_search_new_movie_event_no_results() { - let (async_server, app_arc, _server) = mock_radarr_api( - RequestMethod::Get, - None, - Some(json!([])), - format!( - "{}?term=test%20term", - RadarrEvent::SearchNewMovie.resource() - ) - .as_str(), - ) - .await; + let resource = format!( + "{}?term=test%20term", + RadarrEvent::SearchNewMovie.resource() + ); + let (async_server, app_arc, _server) = + mock_radarr_api(RequestMethod::Get, None, Some(json!([])), &resource).await; app_arc.lock().await.data.radarr_data.search = "test term".to_owned().into(); let network = Network::new(reqwest::Client::new(), &app_arc); @@ -1766,11 +1757,12 @@ mod test { #[tokio::test] async fn test_handle_get_movie_details_event() { + let resource = format!("{}/1", RadarrEvent::GetMovieDetails.resource()); let (async_server, app_arc, _server) = mock_radarr_api( RequestMethod::Get, None, Some(serde_json::from_str(MOVIE_JSON).unwrap()), - format!("{}/1", RadarrEvent::GetMovieDetails.resource()).as_str(), + &resource, ) .await; app_arc @@ -1872,11 +1864,12 @@ mod test { "minimumAvailability": "released", "ratings": {} }); + let resource = format!("{}/1", RadarrEvent::GetMovieDetails.resource()); let (async_server, app_arc, _server) = mock_radarr_api( RequestMethod::Get, None, Some(movie_json_with_missing_fields), - format!("{}/1", RadarrEvent::GetMovieDetails.resource()).as_str(), + &resource, ) .await; app_arc @@ -1953,11 +1946,12 @@ mod test { "date": "2022-12-30T07:37:56Z", "eventType": "grabbed" }]); + let resource = format!("{}?movieId=1", RadarrEvent::GetMovieHistory.resource()); let (async_server, app_arc, _server) = mock_radarr_api( RequestMethod::Get, None, Some(movie_history_item_json), - format!("{}?movieId=1", RadarrEvent::GetMovieHistory.resource()).as_str(), + &resource, ) .await; app_arc @@ -2183,13 +2177,9 @@ mod test { "type": "crew", } ]); - let (async_server, app_arc, _server) = mock_radarr_api( - RequestMethod::Get, - None, - Some(credits_json), - format!("{}?movieId=1", RadarrEvent::GetMovieCredits.resource()).as_str(), - ) - .await; + let resource = format!("{}?movieId=1", RadarrEvent::GetMovieCredits.resource()); + let (async_server, app_arc, _server) = + mock_radarr_api(RequestMethod::Get, None, Some(credits_json), &resource).await; app_arc .lock() .await @@ -2216,17 +2206,12 @@ mod test { #[tokio::test] async fn test_handle_delete_movie_event() { - let (async_server, app_arc, _server) = mock_radarr_api( - RequestMethod::Delete, - None, - None, - format!( - "{}/1?deleteFiles=true&addImportExclusion=true", - RadarrEvent::DeleteMovie.resource() - ) - .as_str(), - ) - .await; + let resource = format!( + "{}/1?deleteFiles=true&addImportExclusion=true", + RadarrEvent::DeleteMovie.resource() + ); + let (async_server, app_arc, _server) = + mock_radarr_api(RequestMethod::Delete, None, None, &resource).await; { let mut app = app_arc.lock().await; app.data.radarr_data.movies.set_items(vec![movie()]); @@ -2244,13 +2229,9 @@ mod test { #[tokio::test] async fn test_handle_delete_download_event() { - let (async_server, app_arc, _server) = mock_radarr_api( - RequestMethod::Delete, - None, - None, - format!("{}/1", RadarrEvent::DeleteDownload.resource()).as_str(), - ) - .await; + let resource = format!("{}/1", RadarrEvent::DeleteDownload.resource()); + let (async_server, app_arc, _server) = + mock_radarr_api(RequestMethod::Delete, None, None, &resource).await; app_arc .lock() .await @@ -2269,13 +2250,9 @@ mod test { #[tokio::test] async fn test_handle_delete_root_folder_event() { - let (async_server, app_arc, _server) = mock_radarr_api( - RequestMethod::Delete, - None, - None, - format!("{}/1", RadarrEvent::DeleteRootFolder.resource()).as_str(), - ) - .await; + let resource = format!("{}/1", RadarrEvent::DeleteRootFolder.resource()); + let (async_server, app_arc, _server) = + mock_radarr_api(RequestMethod::Delete, None, None, &resource).await; app_arc .lock() .await @@ -2415,11 +2392,12 @@ mod test { *expected_body.get_mut("path").unwrap() = json!("/nfs/Test Path"); *expected_body.get_mut("tags").unwrap() = json!([1, 2]); + let resource = format!("{}/1", RadarrEvent::GetMovieDetails.resource()); let (async_details_server, app_arc, mut server) = mock_radarr_api( RequestMethod::Get, None, Some(serde_json::from_str(MOVIE_JSON).unwrap()), - format!("{}/1", RadarrEvent::GetMovieDetails.resource()).as_str(), + &resource, ) .await; let async_edit_server = server @@ -2510,11 +2488,12 @@ mod test { *expected_body.get_mut("rootFolderPath").unwrap() = json!("/nfs/Test Path"); *expected_body.get_mut("searchOnAdd").unwrap() = json!(false); + let resource = format!("{}/123", RadarrEvent::GetCollections.resource()); let (async_details_server, app_arc, mut server) = mock_radarr_api( RequestMethod::Get, None, Some(detailed_collection_body), - format!("{}/123", RadarrEvent::GetCollections.resource()).as_str(), + &resource, ) .await; let async_edit_server = server @@ -2854,7 +2833,7 @@ mod test { request_body: Option, response_body: Option, resource: &str, - ) -> (Mock, Arc>, ServerGuard) { + ) -> (Mock, Arc>>, ServerGuard) { let mut server = Server::new_async().await; let mut async_server = server .mock( diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 4e0fecd..4697232 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -29,7 +29,7 @@ mod utils; static HIGHLIGHT_SYMBOL: &str = "=> "; -pub fn ui(f: &mut Frame<'_, B>, app: &mut App) { +pub fn ui(f: &mut Frame<'_, B>, app: &mut App<'_>) { f.render_widget(background_block(), f.size()); let main_chunks = if !app.error.text.is_empty() { let chunks = vertical_chunks_with_margin( @@ -65,7 +65,7 @@ pub fn ui(f: &mut Frame<'_, B>, app: &mut App) { } } -fn draw_header_row(f: &mut Frame<'_, B>, app: &mut App, area: Rect) { +fn draw_header_row(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect) { let chunks = horizontal_chunks_with_margin(vec![Constraint::Length(75), Constraint::Min(0)], area, 1); let help_text = Text::from(app.server_tabs.get_active_tab_help()); @@ -74,7 +74,7 @@ fn draw_header_row(f: &mut Frame<'_, B>, app: &mut App, area: Rect) .server_tabs .tabs .iter() - .map(|tab| Spans::from(Span::styled(&tab.title, style_default_bold()))) + .map(|tab| Spans::from(Span::styled(tab.title, style_default_bold()))) .collect(); let tabs = Tabs::new(titles) .block(logo_block()) @@ -89,7 +89,7 @@ fn draw_header_row(f: &mut Frame<'_, B>, app: &mut App, area: Rect) f.render_widget(help, chunks[1]); } -fn draw_error(f: &mut Frame<'_, B>, app: &mut App, area: Rect) { +fn draw_error(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect) { let block = title_block("Error | to close").style(style_failure().add_modifier(Modifier::BOLD)); @@ -112,8 +112,8 @@ fn draw_error(f: &mut Frame<'_, B>, app: &mut App, area: Rect) { pub fn draw_popup( f: &mut Frame<'_, B>, - app: &mut App, - popup_fn: fn(&mut Frame<'_, B>, &mut App, Rect), + app: &mut App<'_>, + popup_fn: fn(&mut Frame<'_, B>, &mut App<'_>, Rect), percent_x: u16, percent_y: u16, ) { @@ -125,10 +125,10 @@ pub fn draw_popup( pub fn draw_popup_over( f: &mut Frame<'_, B>, - app: &mut App, + app: &mut App<'_>, area: Rect, - background_fn: fn(&mut Frame<'_, B>, &mut App, Rect), - popup_fn: fn(&mut Frame<'_, B>, &mut App, Rect), + background_fn: fn(&mut Frame<'_, B>, &mut App<'_>, Rect), + popup_fn: fn(&mut Frame<'_, B>, &mut App<'_>, Rect), percent_x: u16, percent_y: u16, ) { @@ -139,55 +139,55 @@ pub fn draw_popup_over( pub fn draw_prompt_popup_over( f: &mut Frame<'_, B>, - app: &mut App, + app: &mut App<'_>, area: Rect, - background_fn: fn(&mut Frame<'_, B>, &mut App, Rect), - popup_fn: fn(&mut Frame<'_, B>, &mut App, Rect), + background_fn: fn(&mut Frame<'_, B>, &mut App<'_>, Rect), + popup_fn: fn(&mut Frame<'_, B>, &mut App<'_>, Rect), ) { draw_popup_over(f, app, area, background_fn, popup_fn, 35, 35); } pub fn draw_small_popup_over( f: &mut Frame<'_, B>, - app: &mut App, + app: &mut App<'_>, area: Rect, - background_fn: fn(&mut Frame<'_, B>, &mut App, Rect), - popup_fn: fn(&mut Frame<'_, B>, &mut App, Rect), + background_fn: fn(&mut Frame<'_, B>, &mut App<'_>, Rect), + popup_fn: fn(&mut Frame<'_, B>, &mut App<'_>, Rect), ) { draw_popup_over(f, app, area, background_fn, popup_fn, 40, 40); } pub fn draw_medium_popup_over( f: &mut Frame<'_, B>, - app: &mut App, + app: &mut App<'_>, area: Rect, - background_fn: fn(&mut Frame<'_, B>, &mut App, Rect), - popup_fn: fn(&mut Frame<'_, B>, &mut App, Rect), + background_fn: fn(&mut Frame<'_, B>, &mut App<'_>, Rect), + popup_fn: fn(&mut Frame<'_, B>, &mut App<'_>, Rect), ) { draw_popup_over(f, app, area, background_fn, popup_fn, 60, 60); } pub fn draw_large_popup_over( f: &mut Frame<'_, B>, - app: &mut App, + app: &mut App<'_>, area: Rect, - background_fn: fn(&mut Frame<'_, B>, &mut App, Rect), - popup_fn: fn(&mut Frame<'_, B>, &mut App, Rect), + background_fn: fn(&mut Frame<'_, B>, &mut App<'_>, Rect), + popup_fn: fn(&mut Frame<'_, B>, &mut App<'_>, Rect), ) { draw_popup_over(f, app, area, background_fn, popup_fn, 75, 75); } pub fn draw_drop_down_popup( f: &mut Frame<'_, B>, - app: &mut App, + app: &mut App<'_>, area: Rect, - background_fn: fn(&mut Frame<'_, B>, &mut App, Rect), - drop_down_fn: fn(&mut Frame<'_, B>, &mut App, Rect), + background_fn: fn(&mut Frame<'_, B>, &mut App<'_>, Rect), + drop_down_fn: fn(&mut Frame<'_, B>, &mut App<'_>, Rect), ) { draw_popup_over(f, app, area, background_fn, drop_down_fn, 20, 30); } -fn draw_context_row(f: &mut Frame<'_, B>, app: &App, area: Rect) { +fn draw_context_row(f: &mut Frame<'_, B>, app: &App<'_>, area: Rect) { if let Route::Radarr(_, _) = app.get_current_route() { radarr_ui::draw_radarr_context_row(f, app, area) } @@ -195,10 +195,10 @@ fn draw_context_row(f: &mut Frame<'_, B>, app: &App, area: Rect) { pub fn draw_error_popup_over( f: &mut Frame<'_, B>, - app: &mut App, + app: &mut App<'_>, area: Rect, message: &str, - background_fn: fn(&mut Frame<'_, B>, &mut App, Rect), + background_fn: fn(&mut Frame<'_, B>, &mut App<'_>, Rect), ) { background_fn(f, app, area); draw_error_popup(f, message); @@ -238,7 +238,7 @@ fn draw_tabs<'a, B: Backend>( let titles = tab_state .tabs .iter() - .map(|tab_route| Spans::from(Span::styled(&tab_route.title, style_default_bold()))) + .map(|tab_route| Spans::from(Span::styled(tab_route.title, style_default_bold()))) .collect(); let tabs = Tabs::new(titles) .block(block) @@ -258,7 +258,7 @@ pub struct TableProps<'a, T> { pub content: &'a mut StatefulTable, pub table_headers: Vec<&'a str>, pub constraints: Vec, - pub help: Option, + pub help: Option<&'static str>, } fn draw_table<'a, B, T, F>( diff --git a/src/ui/radarr_ui/add_movie_ui.rs b/src/ui/radarr_ui/add_movie_ui.rs index 6e1e314..4fa21ba 100644 --- a/src/ui/radarr_ui/add_movie_ui.rs +++ b/src/ui/radarr_ui/add_movie_ui.rs @@ -27,7 +27,7 @@ use crate::App; pub(super) fn draw_add_movie_search_popup( f: &mut Frame<'_, B>, - app: &mut App, + app: &mut App<'_>, area: Rect, ) { if let Route::Radarr(active_radarr_block, context_option) = *app.get_current_route() { @@ -67,7 +67,7 @@ pub(super) fn draw_add_movie_search_popup( } } -fn draw_add_movie_search(f: &mut Frame<'_, B>, app: &mut App, area: Rect) { +fn draw_add_movie_search(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect) { let current_selection = if app.data.radarr_data.add_searched_movies.items.is_empty() { AddMovieSearchResult::default() } else { @@ -233,7 +233,7 @@ fn draw_add_movie_search(f: &mut Frame<'_, B>, app: &mut App, area: ); } -fn draw_confirmation_popup(f: &mut Frame<'_, B>, app: &mut App, prompt_area: Rect) { +fn draw_confirmation_popup(f: &mut Frame<'_, B>, app: &mut App<'_>, prompt_area: Rect) { if let Route::Radarr(active_radarr_block, _) = *app.get_current_route() { match active_radarr_block { ActiveRadarrBlock::AddMovieSelectMonitor => { @@ -280,7 +280,11 @@ fn draw_confirmation_popup(f: &mut Frame<'_, B>, app: &mut App, prom } } -fn draw_select_monitor_popup(f: &mut Frame<'_, B>, app: &mut App, popup_area: Rect) { +fn draw_select_monitor_popup( + f: &mut Frame<'_, B>, + app: &mut App<'_>, + popup_area: Rect, +) { draw_drop_down_list( f, popup_area, @@ -289,7 +293,11 @@ fn draw_select_monitor_popup(f: &mut Frame<'_, B>, app: &mut App, po ); } -fn draw_confirmation_prompt(f: &mut Frame<'_, B>, app: &mut App, prompt_area: Rect) { +fn draw_confirmation_prompt( + f: &mut Frame<'_, B>, + app: &mut App<'_>, + prompt_area: Rect, +) { let (movie_title, movie_overview) = if let Route::Radarr(_, Some(_)) = app.get_current_route() { ( &app @@ -328,8 +336,8 @@ fn draw_confirmation_prompt(f: &mut Frame<'_, B>, app: &mut App, pro 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; - let highlight_yes_no = selected_block == ActiveRadarrBlock::AddMovieConfirmPrompt; + let selected_block = app.data.radarr_data.selected_block.get_active_block(); + let highlight_yes_no = selected_block == &ActiveRadarrBlock::AddMovieConfirmPrompt; let selected_monitor = app.data.radarr_data.monitor_list.current_selection(); let selected_minimum_availability = app @@ -374,7 +382,7 @@ fn draw_confirmation_prompt(f: &mut Frame<'_, B>, app: &mut App, pro chunks[1], "Root Folder", &selected_root_folder.path, - selected_block == ActiveRadarrBlock::AddMovieSelectRootFolder, + selected_block == &ActiveRadarrBlock::AddMovieSelectRootFolder, ); draw_drop_down_menu_button( @@ -382,7 +390,7 @@ fn draw_confirmation_prompt(f: &mut Frame<'_, B>, app: &mut App, pro chunks[2], "Monitor", selected_monitor.to_display_str(), - selected_block == ActiveRadarrBlock::AddMovieSelectMonitor, + selected_block == &ActiveRadarrBlock::AddMovieSelectMonitor, ); draw_drop_down_menu_button( @@ -390,14 +398,14 @@ fn draw_confirmation_prompt(f: &mut Frame<'_, B>, app: &mut App, pro chunks[3], "Minimum Availability", selected_minimum_availability.to_display_str(), - selected_block == ActiveRadarrBlock::AddMovieSelectMinimumAvailability, + selected_block == &ActiveRadarrBlock::AddMovieSelectMinimumAvailability, ); draw_drop_down_menu_button( f, chunks[4], "Quality Profile", selected_quality_profile, - selected_block == ActiveRadarrBlock::AddMovieSelectQualityProfile, + selected_block == &ActiveRadarrBlock::AddMovieSelectQualityProfile, ); if let Route::Radarr(active_radarr_block, _) = *app.get_current_route() { @@ -407,7 +415,7 @@ fn draw_confirmation_prompt(f: &mut Frame<'_, B>, app: &mut App, pro "Tags", &app.data.radarr_data.edit_tags.text, *app.data.radarr_data.edit_tags.offset.borrow(), - selected_block == ActiveRadarrBlock::AddMovieTagsInput, + selected_block == &ActiveRadarrBlock::AddMovieTagsInput, active_radarr_block == ActiveRadarrBlock::AddMovieTagsInput, ); } diff --git a/src/ui/radarr_ui/collection_details_ui.rs b/src/ui/radarr_ui/collection_details_ui.rs index 82bb0bd..12c9a83 100644 --- a/src/ui/radarr_ui/collection_details_ui.rs +++ b/src/ui/radarr_ui/collection_details_ui.rs @@ -18,7 +18,7 @@ use crate::utils::convert_runtime; pub(super) fn draw_collection_details_popup( f: &mut Frame<'_, B>, - app: &mut App, + app: &mut App<'_>, content_area: Rect, ) { if let Route::Radarr(active_radarr_block, context_option) = app.get_current_route() { @@ -40,7 +40,7 @@ pub(super) fn draw_collection_details_popup( pub(super) fn draw_collection_details( f: &mut Frame<'_, B>, - app: &mut App, + app: &mut App<'_>, content_area: Rect, ) { let chunks = vertical_chunks_with_margin( @@ -213,7 +213,7 @@ pub(super) fn draw_collection_details( ); } -fn draw_movie_overview(f: &mut Frame<'_, B>, app: &mut App, content_area: Rect) { +fn draw_movie_overview(f: &mut Frame<'_, B>, app: &mut App<'_>, content_area: Rect) { let title_block = title_block("Overview"); f.render_widget(title_block, content_area); diff --git a/src/ui/radarr_ui/delete_movie_ui.rs b/src/ui/radarr_ui/delete_movie_ui.rs index 0552107..8aa0854 100644 --- a/src/ui/radarr_ui/delete_movie_ui.rs +++ b/src/ui/radarr_ui/delete_movie_ui.rs @@ -9,14 +9,14 @@ use crate::ui::draw_prompt_box_with_checkboxes; pub(super) fn draw_delete_movie_prompt( f: &mut Frame<'_, B>, - app: &mut App, + app: &mut App<'_>, prompt_area: Rect, ) { if matches!( *app.get_current_route(), Route::Radarr(ActiveRadarrBlock::DeleteMoviePrompt, _) ) { - let selected_block = app.data.radarr_data.selected_block; + let selected_block = app.data.radarr_data.selected_block.get_active_block(); draw_prompt_box_with_checkboxes( f, prompt_area, @@ -30,15 +30,15 @@ pub(super) fn draw_delete_movie_prompt( ( "Delete Movie Files", app.data.radarr_data.delete_movie_files, - selected_block == ActiveRadarrBlock::DeleteMovieToggleDeleteFile, + selected_block == &ActiveRadarrBlock::DeleteMovieToggleDeleteFile, ), ( "Add List Exclusion", app.data.radarr_data.add_list_exclusion, - selected_block == ActiveRadarrBlock::DeleteMovieToggleAddListExclusion, + selected_block == &ActiveRadarrBlock::DeleteMovieToggleAddListExclusion, ), ], - selected_block == ActiveRadarrBlock::DeleteMovieConfirmPrompt, + selected_block == &ActiveRadarrBlock::DeleteMovieConfirmPrompt, app.data.radarr_data.prompt_confirm, ) } diff --git a/src/ui/radarr_ui/edit_collection_ui.rs b/src/ui/radarr_ui/edit_collection_ui.rs index 24508ee..9fe2977 100644 --- a/src/ui/radarr_ui/edit_collection_ui.rs +++ b/src/ui/radarr_ui/edit_collection_ui.rs @@ -18,7 +18,7 @@ use crate::ui::{ pub(super) fn draw_edit_collection_prompt( f: &mut Frame<'_, B>, - app: &mut App, + app: &mut App<'_>, prompt_area: Rect, ) { if let Route::Radarr(active_radarr_block, _) = *app.get_current_route() { @@ -54,7 +54,7 @@ pub(super) fn draw_edit_collection_prompt( fn draw_edit_collection_confirmation_prompt( f: &mut Frame<'_, B>, - app: &mut App, + app: &mut App<'_>, prompt_area: Rect, ) { let (collection_title, collection_overview) = @@ -97,8 +97,8 @@ fn draw_edit_collection_confirmation_prompt( }; let title = format!("Edit - {}", collection_title); let yes_no_value = app.data.radarr_data.prompt_confirm; - let selected_block = app.data.radarr_data.selected_block; - let highlight_yes_no = selected_block == ActiveRadarrBlock::EditCollectionConfirmPrompt; + let selected_block = app.data.radarr_data.selected_block.get_active_block(); + let highlight_yes_no = selected_block == &ActiveRadarrBlock::EditCollectionConfirmPrompt; let selected_minimum_availability = app .data @@ -141,7 +141,7 @@ fn draw_edit_collection_confirmation_prompt( chunks[1], "Monitored", app.data.radarr_data.edit_monitored.unwrap_or_default(), - selected_block == ActiveRadarrBlock::EditCollectionToggleMonitored, + selected_block == &ActiveRadarrBlock::EditCollectionToggleMonitored, ); draw_drop_down_menu_button( @@ -149,14 +149,14 @@ fn draw_edit_collection_confirmation_prompt( chunks[2], "Minimum Availability", selected_minimum_availability.to_display_str(), - selected_block == ActiveRadarrBlock::EditCollectionSelectMinimumAvailability, + selected_block == &ActiveRadarrBlock::EditCollectionSelectMinimumAvailability, ); draw_drop_down_menu_button( f, chunks[3], "Quality Profile", selected_quality_profile, - selected_block == ActiveRadarrBlock::EditCollectionSelectQualityProfile, + selected_block == &ActiveRadarrBlock::EditCollectionSelectQualityProfile, ); if let Route::Radarr(active_radarr_block, _) = *app.get_current_route() { @@ -166,7 +166,7 @@ fn draw_edit_collection_confirmation_prompt( "Root Folder", &app.data.radarr_data.edit_path.text, *app.data.radarr_data.edit_path.offset.borrow(), - selected_block == ActiveRadarrBlock::EditCollectionRootFolderPathInput, + selected_block == &ActiveRadarrBlock::EditCollectionRootFolderPathInput, active_radarr_block == ActiveRadarrBlock::EditCollectionRootFolderPathInput, ); } @@ -176,7 +176,7 @@ fn draw_edit_collection_confirmation_prompt( chunks[5], "Search on Add", app.data.radarr_data.edit_search_on_add.unwrap_or_default(), - selected_block == ActiveRadarrBlock::EditCollectionToggleSearchOnAdd, + selected_block == &ActiveRadarrBlock::EditCollectionToggleSearchOnAdd, ); draw_button( diff --git a/src/ui/radarr_ui/edit_movie_ui.rs b/src/ui/radarr_ui/edit_movie_ui.rs index 2c24dd0..b906448 100644 --- a/src/ui/radarr_ui/edit_movie_ui.rs +++ b/src/ui/radarr_ui/edit_movie_ui.rs @@ -18,7 +18,7 @@ use crate::ui::{ pub(super) fn draw_edit_movie_prompt( f: &mut Frame<'_, B>, - app: &mut App, + app: &mut App<'_>, prompt_area: Rect, ) { if let Route::Radarr(active_radarr_block, _) = *app.get_current_route() { @@ -54,7 +54,7 @@ pub(super) fn draw_edit_movie_prompt( fn draw_edit_movie_confirmation_prompt( f: &mut Frame<'_, B>, - app: &mut App, + app: &mut App<'_>, prompt_area: Rect, ) { let (movie_title, movie_overview) = if app.data.radarr_data.filtered_movies.items.is_empty() { @@ -94,8 +94,8 @@ fn draw_edit_movie_confirmation_prompt( }; let title = format!("Edit - {}", movie_title); let yes_no_value = app.data.radarr_data.prompt_confirm; - let selected_block = app.data.radarr_data.selected_block; - let highlight_yes_no = selected_block == ActiveRadarrBlock::EditMovieConfirmPrompt; + let selected_block = app.data.radarr_data.selected_block.get_active_block(); + let highlight_yes_no = selected_block == &ActiveRadarrBlock::EditMovieConfirmPrompt; let selected_minimum_availability = app .data @@ -138,7 +138,7 @@ fn draw_edit_movie_confirmation_prompt( chunks[1], "Monitored", app.data.radarr_data.edit_monitored.unwrap_or_default(), - selected_block == ActiveRadarrBlock::EditMovieToggleMonitored, + selected_block == &ActiveRadarrBlock::EditMovieToggleMonitored, ); draw_drop_down_menu_button( @@ -146,14 +146,14 @@ fn draw_edit_movie_confirmation_prompt( chunks[2], "Minimum Availability", selected_minimum_availability.to_display_str(), - selected_block == ActiveRadarrBlock::EditMovieSelectMinimumAvailability, + selected_block == &ActiveRadarrBlock::EditMovieSelectMinimumAvailability, ); draw_drop_down_menu_button( f, chunks[3], "Quality Profile", selected_quality_profile, - selected_block == ActiveRadarrBlock::EditMovieSelectQualityProfile, + selected_block == &ActiveRadarrBlock::EditMovieSelectQualityProfile, ); if let Route::Radarr(active_radarr_block, _) = *app.get_current_route() { @@ -163,7 +163,7 @@ fn draw_edit_movie_confirmation_prompt( "Path", &app.data.radarr_data.edit_path.text, *app.data.radarr_data.edit_path.offset.borrow(), - selected_block == ActiveRadarrBlock::EditMoviePathInput, + selected_block == &ActiveRadarrBlock::EditMoviePathInput, active_radarr_block == ActiveRadarrBlock::EditMoviePathInput, ); draw_text_box_with_label( @@ -172,7 +172,7 @@ fn draw_edit_movie_confirmation_prompt( "Tags", &app.data.radarr_data.edit_tags.text, *app.data.radarr_data.edit_tags.offset.borrow(), - selected_block == ActiveRadarrBlock::EditMovieTagsInput, + selected_block == &ActiveRadarrBlock::EditMovieTagsInput, active_radarr_block == ActiveRadarrBlock::EditMovieTagsInput, ); } diff --git a/src/ui/radarr_ui/mod.rs b/src/ui/radarr_ui/mod.rs index 85802b9..529bacf 100644 --- a/src/ui/radarr_ui/mod.rs +++ b/src/ui/radarr_ui/mod.rs @@ -43,7 +43,7 @@ mod edit_collection_ui; mod edit_movie_ui; mod movie_details_ui; -pub(super) fn draw_radarr_ui(f: &mut Frame<'_, B>, app: &mut App, area: Rect) { +pub(super) fn draw_radarr_ui(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect) { let (content_rect, _) = draw_tabs(f, area, "Movies", &app.data.radarr_data.main_tabs); if let Route::Radarr(active_radarr_block, context_option) = *app.get_current_route() { @@ -195,7 +195,7 @@ pub(super) fn draw_radarr_ui(f: &mut Frame<'_, B>, app: &mut App, ar } } -pub(super) fn draw_radarr_context_row(f: &mut Frame<'_, B>, app: &App, area: Rect) { +pub(super) fn draw_radarr_context_row(f: &mut Frame<'_, B>, app: &App<'_>, area: Rect) { let chunks = horizontal_chunks(vec![Constraint::Min(0), Constraint::Length(20)], area); let context_chunks = horizontal_chunks( @@ -208,7 +208,7 @@ pub(super) fn draw_radarr_context_row(f: &mut Frame<'_, B>, app: &Ap draw_radarr_logo(f, chunks[1]); } -fn draw_library(f: &mut Frame<'_, B>, app: &mut App, area: Rect) { +fn draw_library(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect) { let current_selection = if !app.data.radarr_data.filtered_movies.items.is_empty() { app .data @@ -314,7 +314,7 @@ fn draw_library(f: &mut Frame<'_, B>, app: &mut App, area: Rect) { fn draw_update_all_movies_prompt( f: &mut Frame<'_, B>, - app: &mut App, + app: &mut App<'_>, prompt_area: Rect, ) { draw_prompt_box( @@ -328,7 +328,7 @@ fn draw_update_all_movies_prompt( fn draw_update_downloads_prompt( f: &mut Frame<'_, B>, - app: &mut App, + app: &mut App<'_>, prompt_area: Rect, ) { draw_prompt_box( @@ -342,7 +342,7 @@ fn draw_update_downloads_prompt( fn draw_update_all_collections_prompt( f: &mut Frame<'_, B>, - app: &mut App, + app: &mut App<'_>, prompt_area: Rect, ) { draw_prompt_box( @@ -354,7 +354,11 @@ fn draw_update_all_collections_prompt( ); } -fn draw_delete_download_prompt(f: &mut Frame<'_, B>, app: &mut App, prompt_area: Rect) { +fn draw_delete_download_prompt( + f: &mut Frame<'_, B>, + app: &mut App<'_>, + prompt_area: Rect, +) { draw_prompt_box( f, prompt_area, @@ -370,7 +374,7 @@ fn draw_delete_download_prompt(f: &mut Frame<'_, B>, app: &mut App, fn draw_delete_root_folder_prompt( f: &mut Frame<'_, B>, - app: &mut App, + app: &mut App<'_>, prompt_area: Rect, ) { draw_prompt_box( @@ -386,7 +390,11 @@ fn draw_delete_root_folder_prompt( ); } -fn draw_add_root_folder_prompt_box(f: &mut Frame<'_, B>, app: &mut App, area: Rect) { +fn draw_add_root_folder_prompt_box( + f: &mut Frame<'_, B>, + app: &mut App<'_>, + area: Rect, +) { let chunks = vertical_chunks_with_margin( vec![ Constraint::Length(3), @@ -413,7 +421,7 @@ fn draw_add_root_folder_prompt_box(f: &mut Frame<'_, B>, app: &mut A f.render_widget(help, chunks[1]); } -fn draw_search_box(f: &mut Frame<'_, B>, app: &mut App, area: Rect) { +fn draw_search_box(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect) { let chunks = vertical_chunks_with_margin(vec![Constraint::Length(3), Constraint::Min(0)], area, 1); if !app.data.radarr_data.is_searching { @@ -454,7 +462,7 @@ fn draw_search_box(f: &mut Frame<'_, B>, app: &mut App, area: Rect) } } -fn draw_filter_box(f: &mut Frame<'_, B>, app: &mut App, area: Rect) { +fn draw_filter_box(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect) { let chunks = vertical_chunks_with_margin(vec![Constraint::Length(3), Constraint::Min(0)], area, 1); if !app.data.radarr_data.is_filtering { @@ -504,7 +512,7 @@ fn draw_radarr_logo(f: &mut Frame<'_, B>, area: Rect) { f.render_widget(logo, area); } -fn draw_stats_context(f: &mut Frame<'_, B>, app: &App, area: Rect) { +fn draw_stats_context(f: &mut Frame<'_, B>, app: &App<'_>, area: Rect) { let block = title_block("Stats"); if !app.data.radarr_data.version.is_empty() { @@ -602,7 +610,7 @@ fn draw_stats_context(f: &mut Frame<'_, B>, app: &App, area: Rect) { } } -fn draw_downloads_context(f: &mut Frame<'_, B>, app: &App, area: Rect) { +fn draw_downloads_context(f: &mut Frame<'_, B>, app: &App<'_>, area: Rect) { let block = title_block("Downloads"); let downloads_vec = &app.data.radarr_data.downloads.items; @@ -632,7 +640,7 @@ fn draw_downloads_context(f: &mut Frame<'_, B>, app: &App, area: Rec } } -fn draw_downloads(f: &mut Frame<'_, B>, app: &mut App, area: Rect) { +fn draw_downloads(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect) { let current_selection = if app.data.radarr_data.downloads.items.is_empty() { DownloadRecord::default() } else { @@ -708,7 +716,7 @@ fn draw_downloads(f: &mut Frame<'_, B>, app: &mut App, area: Rect) { ); } -fn draw_collections(f: &mut Frame<'_, B>, app: &mut App, area: Rect) { +fn draw_collections(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect) { let current_selection = if !app.data.radarr_data.filtered_collections.items.is_empty() { app .data @@ -790,7 +798,7 @@ fn draw_collections(f: &mut Frame<'_, B>, app: &mut App, area: Rect) ); } -fn draw_root_folders(f: &mut Frame<'_, B>, app: &mut App, area: Rect) { +fn draw_root_folders(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect) { draw_table( f, area, @@ -863,7 +871,7 @@ fn determine_row_style(downloads_vec: &[DownloadRecord], movie: &Movie) -> Style fn draw_select_minimum_availability_popup( f: &mut Frame<'_, B>, - app: &mut App, + app: &mut App<'_>, popup_area: Rect, ) { draw_drop_down_list( @@ -876,7 +884,7 @@ fn draw_select_minimum_availability_popup( fn draw_select_quality_profile_popup( f: &mut Frame<'_, B>, - app: &mut App, + app: &mut App<'_>, popup_area: Rect, ) { draw_drop_down_list( @@ -889,7 +897,7 @@ fn draw_select_quality_profile_popup( fn draw_select_root_folder_popup( f: &mut Frame<'_, B>, - app: &mut App, + app: &mut App<'_>, popup_area: Rect, ) { draw_drop_down_list( diff --git a/src/ui/radarr_ui/movie_details_ui.rs b/src/ui/radarr_ui/movie_details_ui.rs index 8ac93fa..6e437de 100644 --- a/src/ui/radarr_ui/movie_details_ui.rs +++ b/src/ui/radarr_ui/movie_details_ui.rs @@ -22,7 +22,11 @@ use crate::ui::{ }; use crate::utils::convert_to_gb; -pub(super) fn draw_movie_info_popup(f: &mut Frame<'_, B>, app: &mut App, area: Rect) { +pub(super) fn draw_movie_info_popup( + f: &mut Frame<'_, B>, + app: &mut App<'_>, + area: Rect, +) { let (content_area, _) = draw_tabs(f, area, "Movie Info", &app.data.radarr_data.movie_info_tabs); if let Route::Radarr(active_radarr_block, context_option) = app.get_current_route() { @@ -67,7 +71,7 @@ pub(super) fn draw_movie_info_popup(f: &mut Frame<'_, B>, app: &mut } } -fn draw_movie_info(f: &mut Frame<'_, B>, app: &mut App, area: Rect) { +fn draw_movie_info(f: &mut Frame<'_, B>, app: &mut App<'_>, area: Rect) { if let Route::Radarr(active_radarr_block, _) = app.data.radarr_data.movie_info_tabs.get_active_route() { @@ -83,7 +87,11 @@ fn draw_movie_info(f: &mut Frame<'_, B>, app: &mut App, area: Rect) } } -fn draw_search_movie_prompt(f: &mut Frame<'_, B>, app: &mut App, prompt_area: Rect) { +fn draw_search_movie_prompt( + f: &mut Frame<'_, B>, + app: &mut App<'_>, + prompt_area: Rect, +) { draw_prompt_box( f, prompt_area, @@ -97,7 +105,11 @@ fn draw_search_movie_prompt(f: &mut Frame<'_, B>, app: &mut App, pro ); } -fn draw_update_and_scan_prompt(f: &mut Frame<'_, B>, app: &mut App, prompt_area: Rect) { +fn draw_update_and_scan_prompt( + f: &mut Frame<'_, B>, + app: &mut App<'_>, + prompt_area: Rect, +) { draw_prompt_box( f, prompt_area, @@ -111,7 +123,7 @@ fn draw_update_and_scan_prompt(f: &mut Frame<'_, B>, app: &mut App, ); } -fn draw_file_info(f: &mut Frame<'_, B>, app: &App, content_area: Rect) { +fn draw_file_info(f: &mut Frame<'_, B>, app: &App<'_>, content_area: Rect) { let file_info = app.data.radarr_data.file_details.to_owned(); if !file_info.is_empty() { @@ -167,7 +179,7 @@ fn draw_file_info(f: &mut Frame<'_, B>, app: &App, content_area: Rec } } -fn draw_movie_details(f: &mut Frame<'_, B>, app: &App, content_area: Rect) { +fn draw_movie_details(f: &mut Frame<'_, B>, app: &App<'_>, content_area: Rect) { let movie_details = app.data.radarr_data.movie_details.get_text(); let block = layout_block_top_border(); @@ -210,7 +222,7 @@ fn draw_movie_details(f: &mut Frame<'_, B>, app: &App, content_area: } } -fn draw_movie_history(f: &mut Frame<'_, B>, app: &mut App, content_area: Rect) { +fn draw_movie_history(f: &mut Frame<'_, B>, app: &mut App<'_>, content_area: Rect) { let current_selection = if app.data.radarr_data.movie_history.items.is_empty() { MovieHistoryItem::default() } else { @@ -285,7 +297,7 @@ fn draw_movie_history(f: &mut Frame<'_, B>, app: &mut App, content_a } } -fn draw_movie_cast(f: &mut Frame<'_, B>, app: &mut App, content_area: Rect) { +fn draw_movie_cast(f: &mut Frame<'_, B>, app: &mut App<'_>, content_area: Rect) { draw_table( f, content_area, @@ -317,7 +329,7 @@ fn draw_movie_cast(f: &mut Frame<'_, B>, app: &mut App, content_area ); } -fn draw_movie_crew(f: &mut Frame<'_, B>, app: &mut App, content_area: Rect) { +fn draw_movie_crew(f: &mut Frame<'_, B>, app: &mut App<'_>, content_area: Rect) { draw_table( f, content_area, @@ -351,7 +363,7 @@ fn draw_movie_crew(f: &mut Frame<'_, B>, app: &mut App, content_area ); } -fn draw_movie_releases(f: &mut Frame<'_, B>, app: &mut App, content_area: Rect) { +fn draw_movie_releases(f: &mut Frame<'_, B>, app: &mut App<'_>, content_area: Rect) { let current_selection = if app.data.radarr_data.movie_releases.items.is_empty() { Release::default() } else { @@ -475,7 +487,7 @@ fn draw_movie_releases(f: &mut Frame<'_, B>, app: &mut App, content_ fn draw_manual_search_confirm_prompt( f: &mut Frame<'_, B>, - app: &mut App, + app: &mut App<'_>, prompt_area: Rect, ) { let current_selection = app.data.radarr_data.movie_releases.current_selection();