Add manual input elements to the app an ui #216

Merged
wojtek merged 17 commits from 188---add-option-for-manual-input-during-fetch into main 2024-09-15 15:20:11 +02:00
3 changed files with 147 additions and 37 deletions
Showing only changes of commit b597edc86f - Show all commits

View File

@ -45,8 +45,8 @@ impl IAppInput for AppInputMode {
fn confirm(mut self) -> Self::APP { fn confirm(mut self) -> Self::APP {
if let AppState::Match(state) = &mut self.app { if let AppState::Match(state) = &mut self.app {
state.submit_input(self.input) state.submit_input(self.input);
}; }
self.app self.app
} }
@ -54,3 +54,45 @@ impl IAppInput for AppInputMode {
self.app self.app
} }
} }
#[cfg(test)]
mod tests {
use crate::tui::app::{
machine::tests::{events, mb_api, music_hoard_init},
IApp,
};
use super::*;
fn input_event(c: char) -> InputEvent {
InputEvent::new(
crossterm::event::KeyCode::Char(c),
crossterm::event::KeyModifiers::empty(),
)
}
#[test]
fn handle_input() {
let mut app = App::new(music_hoard_init(vec![]), mb_api(), events());
app.input_mut().replace(Input::default());
let input = app.mode().unwrap_input();
let app = input.input(input_event('H'));
let input = app.mode().unwrap_input();
let app = input.input(input_event('e'));
let input = app.mode().unwrap_input();
let app = input.input(input_event('l'));
let input = app.mode().unwrap_input();
let app = input.input(input_event('l'));
let input = app.mode().unwrap_input();
let app = input.input(input_event('o'));
assert_eq!(app.input_ref().as_ref().unwrap().0.value(), "Hello");
app.mode().unwrap_input().confirm().unwrap_browse();
}
}

View File

@ -177,7 +177,7 @@ mod tests {
use crate::tui::{ use crate::tui::{
app::{ app::{
machine::tests::{inner, music_hoard}, machine::tests::{inner, music_hoard},
IAppAccess, IApp, IAppAccess, IAppInput,
}, },
lib::interface::musicbrainz::Match, lib::interface::musicbrainz::Match,
}; };
@ -362,4 +362,21 @@ mod tests {
let matches = AppMachine::match_state(inner(music_hoard(vec![])), match_state(None)); let matches = AppMachine::match_state(inner(music_hoard(vec![])), match_state(None));
matches.select().unwrap_browse(); matches.select().unwrap_browse();
} }
#[test]
fn select_manual_input() {
let matches =
AppMachine::match_state(inner(music_hoard(vec![])), match_state(Some(album_match())));
// album_match has two matches which means that the fourth option should be manual input.
let matches = matches.next_match().unwrap_match();
let matches = matches.next_match().unwrap_match();
let matches = matches.next_match().unwrap_match();
let matches = matches.next_match().unwrap_match();
let app = matches.select();
let input = app.mode().unwrap_input();
input.confirm().unwrap_match();
}
} }

View File

@ -54,6 +54,36 @@ pub struct AppInner {
events: EventSender, events: EventSender,
} }
macro_rules! app_field_ref {
($app:ident, $field:ident) => {
match $app {
AppState::Browse(state) => &state.$field,
AppState::Info(state) => &state.$field,
AppState::Reload(state) => &state.$field,
AppState::Search(state) => &state.$field,
AppState::Fetch(state) => &state.$field,
AppState::Match(state) => &state.$field,
AppState::Error(state) => &state.$field,
AppState::Critical(state) => &state.$field,
}
};
}
macro_rules! app_field_mut {
($app:ident, $field:ident) => {
match $app {
AppState::Browse(state) => &mut state.$field,
AppState::Info(state) => &mut state.$field,
AppState::Reload(state) => &mut state.$field,
AppState::Search(state) => &mut state.$field,
AppState::Fetch(state) => &mut state.$field,
AppState::Match(state) => &mut state.$field,
AppState::Error(state) => &mut state.$field,
AppState::Critical(state) => &mut state.$field,
}
};
}
impl App { impl App {
pub fn new<MH: IMusicHoard + 'static, MB: IMusicBrainz + Send + 'static>( pub fn new<MH: IMusicHoard + 'static, MB: IMusicBrainz + Send + 'static>(
mut music_hoard: MH, mut music_hoard: MH,
@ -74,42 +104,20 @@ impl App {
} }
fn inner_ref(&self) -> &AppInner { fn inner_ref(&self) -> &AppInner {
match self { app_field_ref!(self, inner)
AppState::Browse(browse_state) => &browse_state.inner,
AppState::Info(info_state) => &info_state.inner,
AppState::Reload(reload_state) => &reload_state.inner,
AppState::Search(search_state) => &search_state.inner,
AppState::Fetch(fetch_state) => &fetch_state.inner,
AppState::Match(match_state) => &match_state.inner,
AppState::Error(error_state) => &error_state.inner,
AppState::Critical(critical_state) => &critical_state.inner,
}
} }
fn inner_mut(&mut self) -> &mut AppInner { fn inner_mut(&mut self) -> &mut AppInner {
match self { app_field_mut!(self, inner)
AppState::Browse(browse_state) => &mut browse_state.inner,
AppState::Info(info_state) => &mut info_state.inner,
AppState::Reload(reload_state) => &mut reload_state.inner,
AppState::Search(search_state) => &mut search_state.inner,
AppState::Fetch(fetch_state) => &mut fetch_state.inner,
AppState::Match(match_state) => &mut match_state.inner,
AppState::Error(error_state) => &mut error_state.inner,
AppState::Critical(critical_state) => &mut critical_state.inner,
} }
#[cfg(test)]
fn input_ref(&self) -> &Option<Input> {
app_field_ref!(self, input)
} }
fn input_mut(&mut self) -> &mut Option<Input> { fn input_mut(&mut self) -> &mut Option<Input> {
match self { app_field_mut!(self, input)
AppState::Browse(state) => &mut state.input,
AppState::Info(state) => &mut state.input,
AppState::Reload(state) => &mut state.input,
AppState::Search(state) => &mut state.input,
AppState::Fetch(state) => &mut state.input,
AppState::Match(state) => &mut state.input,
AppState::Error(state) => &mut state.input,
AppState::Critical(state) => &mut state.input,
}
} }
} }
@ -221,13 +229,29 @@ mod tests {
use musichoard::collection::Collection; use musichoard::collection::Collection;
use crate::tui::{ use crate::tui::{
app::{AppState, IApp, IAppInteractBrowse}, app::{AppState, IApp, IAppInput, IAppInteractBrowse},
lib::{interface::musicbrainz::MockIMusicBrainz, MockIMusicHoard}, lib::{interface::musicbrainz::MockIMusicBrainz, MockIMusicHoard},
EventChannel, EventChannel,
}; };
use super::*; use super::*;
impl<StateMode, InputMode> AppMode<StateMode, InputMode> {
fn unwrap_state(self) -> StateMode {
match self {
AppMode::State(state) => state,
_ => panic!(),
}
}
pub fn unwrap_input(self) -> InputMode {
match self {
AppMode::Input(input) => input,
_ => panic!(),
}
}
}
impl< impl<
BrowseState, BrowseState,
InfoState, InfoState,
@ -313,7 +337,7 @@ mod tests {
music_hoard music_hoard
} }
fn music_hoard_init(collection: Collection) -> MockIMusicHoard { pub fn music_hoard_init(collection: Collection) -> MockIMusicHoard {
let mut music_hoard = music_hoard(collection); let mut music_hoard = music_hoard(collection);
music_hoard music_hoard
@ -324,11 +348,11 @@ mod tests {
music_hoard music_hoard
} }
fn mb_api() -> MockIMusicBrainz { pub fn mb_api() -> MockIMusicBrainz {
MockIMusicBrainz::new() MockIMusicBrainz::new()
} }
fn events() -> EventSender { pub fn events() -> EventSender {
EventChannel::new().sender() EventChannel::new().sender()
} }
@ -340,6 +364,33 @@ mod tests {
AppInner::new(music_hoard, mb_api, events()) AppInner::new(music_hoard, mb_api, events())
} }
#[test]
fn input_mode() {
let app = App::new(music_hoard_init(vec![]), mb_api(), events());
assert!(app.is_running());
let mode = app.mode();
assert!(matches!(mode, AppMode::State(_)));
let state = mode.unwrap_state();
assert!(matches!(state, AppState::Browse(_)));
let mut app = state;
app.input_mut().replace(Input::default());
let public = app.get();
assert!(public.input.is_some());
let mode = app.mode();
assert!(matches!(mode, AppMode::Input(_)));
let mut app = mode.unwrap_input().cancel();
assert!(matches!(app, AppState::Browse(_)));
let public = app.get();
assert!(public.input.is_none());
}
#[test] #[test]
fn state_browse() { fn state_browse() {
let mut app = App::new(music_hoard_init(vec![]), mb_api(), events()); let mut app = App::new(music_hoard_init(vec![]), mb_api(), events());