1use anyhow::Result;
14use crossterm::event::{
15 self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEventKind,
16};
17use crossterm::execute;
18use crossterm::terminal::{
19 disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen,
20};
21use ratatui::backend::CrosstermBackend;
22use ratatui::style::Color;
23use ratatui::Terminal;
24use std::time::Duration;
25
26use crate::client::RommClient;
27use crate::config::Config;
28use crate::core::cache::{RomCache, RomCacheKey};
29use crate::core::download::DownloadManager;
30use crate::endpoints::{collections::ListCollections, platforms::ListPlatforms, roms::GetRoms};
31use crate::types::RomList;
32
33use super::openapi::{resolve_path_template, EndpointRegistry};
34use super::screens::connected_splash::{self, StartupSplash};
35use super::screens::setup_wizard::SetupWizard;
36use super::screens::{
37 BrowseScreen, DownloadScreen, ExecuteScreen, GameDetailPrevious, GameDetailScreen,
38 LibraryBrowseScreen, MainMenuScreen, ResultDetailScreen, ResultScreen, SearchScreen,
39 SettingsScreen,
40};
41
42pub enum AppScreen {
51 MainMenu(MainMenuScreen),
52 LibraryBrowse(LibraryBrowseScreen),
53 Search(SearchScreen),
54 Settings(SettingsScreen),
55 Browse(BrowseScreen),
56 Execute(ExecuteScreen),
57 Result(ResultScreen),
58 ResultDetail(ResultDetailScreen),
59 GameDetail(Box<GameDetailScreen>),
60 Download(DownloadScreen),
61 SetupWizard(Box<crate::tui::screens::setup_wizard::SetupWizard>),
62}
63
64pub struct App {
73 screen: AppScreen,
74 client: RommClient,
75 config: Config,
76 registry: EndpointRegistry,
77 server_version: Option<String>,
79 rom_cache: RomCache,
80 downloads: DownloadManager,
81 screen_before_download: Option<AppScreen>,
83 deferred_load_roms: Option<(Option<RomCacheKey>, Option<GetRoms>, u64)>,
85 startup_splash: Option<StartupSplash>,
87}
88
89impl App {
90 pub fn new(
92 client: RommClient,
93 config: Config,
94 registry: EndpointRegistry,
95 server_version: Option<String>,
96 startup_splash: Option<StartupSplash>,
97 ) -> Self {
98 Self {
99 screen: AppScreen::MainMenu(MainMenuScreen::new()),
100 client,
101 config,
102 registry,
103 server_version,
104 rom_cache: RomCache::load(),
105 downloads: DownloadManager::new(),
106 screen_before_download: None,
107 deferred_load_roms: None,
108 startup_splash,
109 }
110 }
111
112 pub async fn run(&mut self) -> Result<()> {
122 enable_raw_mode()?;
123 let mut stdout = std::io::stdout();
124 execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
125 let backend = CrosstermBackend::new(stdout);
126 let mut terminal = Terminal::new(backend)?;
127
128 loop {
129 if self
130 .startup_splash
131 .as_ref()
132 .is_some_and(|s| s.should_auto_dismiss())
133 {
134 self.startup_splash = None;
135 }
136 terminal.draw(|f| self.render(f))?;
139
140 if event::poll(Duration::from_millis(100))? {
143 if let Event::Key(key) = event::read()? {
144 if key.kind == KeyEventKind::Press && self.handle_key(key.code).await? {
145 break;
146 }
147 }
148 }
149
150 if let Some((key, req, expected)) = self.deferred_load_roms.take() {
155 if let Ok(Some(roms)) = self.load_roms_cached(key, req, expected).await {
156 if let AppScreen::LibraryBrowse(ref mut lib) = self.screen {
157 lib.set_roms(roms);
158 }
159 }
160 }
161 }
162
163 disable_raw_mode()?;
164 execute!(
165 terminal.backend_mut(),
166 LeaveAlternateScreen,
167 DisableMouseCapture
168 )?;
169 terminal.show_cursor()?;
170 Ok(())
171 }
172
173 async fn load_roms_cached(
180 &mut self,
181 key: Option<RomCacheKey>,
182 req: Option<GetRoms>,
183 expected_count: u64,
184 ) -> Result<Option<RomList>> {
185 if let Some(k) = key {
187 if let Some(cached) = self.rom_cache.get_valid(&k, expected_count) {
188 return Ok(Some(cached.clone()));
189 }
190 }
191 if let Some(r) = req {
193 let mut roms = self.client.call(&r).await?;
194 let total = roms.total;
195 let ceiling = 20000;
196
197 while (roms.items.len() as u64) < total && (roms.items.len() as u64) < ceiling {
200 let mut next_req = r.clone();
201 next_req.offset = Some(roms.items.len() as u32);
202
203 let next_batch = self.client.call(&next_req).await?;
204 if next_batch.items.is_empty() {
205 break;
206 }
207 roms.items.extend(next_batch.items);
208 }
209
210 if let Some(k) = key {
211 self.rom_cache.insert(k, roms.clone(), expected_count); }
213 return Ok(Some(roms));
214 }
215 Ok(None)
216 }
217
218 async fn handle_key(&mut self, key: KeyCode) -> Result<bool> {
223 if self.startup_splash.is_some() {
224 self.startup_splash = None;
225 return Ok(false);
226 }
227
228 if key == KeyCode::Char('d') && !matches!(&self.screen, AppScreen::Search(_)) {
230 self.toggle_download_screen();
231 return Ok(false);
232 }
233
234 match &self.screen {
235 AppScreen::MainMenu(_) => self.handle_main_menu(key).await,
236 AppScreen::LibraryBrowse(_) => self.handle_library_browse(key).await,
237 AppScreen::Search(_) => self.handle_search(key).await,
238 AppScreen::Settings(_) => self.handle_settings(key),
239 AppScreen::Browse(_) => self.handle_browse(key),
240 AppScreen::Execute(_) => self.handle_execute(key).await,
241 AppScreen::Result(_) => self.handle_result(key),
242 AppScreen::ResultDetail(_) => self.handle_result_detail(key),
243 AppScreen::GameDetail(_) => self.handle_game_detail(key),
244 AppScreen::Download(_) => self.handle_download(key),
245 AppScreen::SetupWizard(_) => self.handle_setup_wizard(key).await,
246 }
247 }
248
249 fn toggle_download_screen(&mut self) {
252 let current =
253 std::mem::replace(&mut self.screen, AppScreen::MainMenu(MainMenuScreen::new()));
254 match current {
255 AppScreen::Download(_) => {
256 self.screen = self
257 .screen_before_download
258 .take()
259 .unwrap_or_else(|| AppScreen::MainMenu(MainMenuScreen::new()));
260 }
261 other => {
262 self.screen_before_download = Some(other);
263 self.screen = AppScreen::Download(DownloadScreen::new(self.downloads.shared()));
264 }
265 }
266 }
267
268 fn handle_download(&mut self, key: KeyCode) -> Result<bool> {
269 if key == KeyCode::Esc || key == KeyCode::Char('d') {
270 self.screen = self
271 .screen_before_download
272 .take()
273 .unwrap_or_else(|| AppScreen::MainMenu(MainMenuScreen::new()));
274 }
275 Ok(false)
276 }
277
278 async fn handle_main_menu(&mut self, key: KeyCode) -> Result<bool> {
281 let menu = match &mut self.screen {
282 AppScreen::MainMenu(m) => m,
283 _ => return Ok(false),
284 };
285 match key {
286 KeyCode::Up | KeyCode::Char('k') => menu.previous(),
287 KeyCode::Down | KeyCode::Char('j') => menu.next(),
288 KeyCode::Enter => match menu.selected {
289 0 => {
290 let platforms = self.client.call(&ListPlatforms).await?;
291 let collections = self.client.call(&ListCollections).await.unwrap_or_default();
292 let mut lib = LibraryBrowseScreen::new(platforms, collections);
293 if lib.list_len() > 0 {
294 let key = lib.cache_key();
295 let expected = lib.expected_rom_count();
296 let req = lib
297 .get_roms_request_platform()
298 .or_else(|| lib.get_roms_request_collection());
299 if let Ok(Some(roms)) = self.load_roms_cached(key, req, expected).await {
300 lib.set_roms(roms);
301 }
302 }
303 self.screen = AppScreen::LibraryBrowse(lib);
304 }
305 1 => self.screen = AppScreen::Search(SearchScreen::new()),
306 2 => {
307 self.screen_before_download = Some(AppScreen::MainMenu(MainMenuScreen::new()));
308 self.screen = AppScreen::Download(DownloadScreen::new(self.downloads.shared()));
309 }
310 3 => {
311 self.screen = AppScreen::Settings(SettingsScreen::new(
312 &self.config,
313 self.server_version.as_deref(),
314 ))
315 }
316 4 => self.screen = AppScreen::Browse(BrowseScreen::new(self.registry.clone())),
317 5 => return Ok(true),
318 _ => {}
319 },
320 KeyCode::Esc | KeyCode::Char('q') => return Ok(true),
321 _ => {}
322 }
323 Ok(false)
324 }
325
326 async fn handle_library_browse(&mut self, key: KeyCode) -> Result<bool> {
329 use super::screens::library_browse::{LibrarySearchMode, LibraryViewMode};
330
331 let lib = match &mut self.screen {
332 AppScreen::LibraryBrowse(l) => l,
333 _ => return Ok(false),
334 };
335
336 if let Some(mode) = lib.search_mode {
338 match key {
339 KeyCode::Esc => lib.clear_search(),
340 KeyCode::Backspace => lib.delete_search_char(),
341 KeyCode::Char(c) => lib.add_search_char(c),
342 KeyCode::Tab if mode == LibrarySearchMode::Jump => lib.jump_to_match(true),
343 KeyCode::Enter => lib.search_mode = None, _ => {}
345 }
346 return Ok(false);
347 }
348
349 match key {
350 KeyCode::Up | KeyCode::Char('k') => {
351 if lib.view_mode == LibraryViewMode::List {
352 lib.list_previous();
353 if lib.list_len() > 0 {
354 lib.clear_roms(); let key = lib.cache_key();
356 let expected = lib.expected_rom_count();
357 let req = lib
358 .get_roms_request_platform()
359 .or_else(|| lib.get_roms_request_collection());
360 self.deferred_load_roms = Some((key, req, expected));
361 }
362 } else {
363 lib.rom_previous();
364 }
365 }
366 KeyCode::Down | KeyCode::Char('j') => {
367 if lib.view_mode == LibraryViewMode::List {
368 lib.list_next();
369 if lib.list_len() > 0 {
370 lib.clear_roms(); let key = lib.cache_key();
372 let expected = lib.expected_rom_count();
373 let req = lib
374 .get_roms_request_platform()
375 .or_else(|| lib.get_roms_request_collection());
376 self.deferred_load_roms = Some((key, req, expected));
377 }
378 } else {
379 lib.rom_next();
380 }
381 }
382 KeyCode::Left | KeyCode::Char('h') => {
383 if lib.view_mode == LibraryViewMode::Roms {
384 lib.back_to_list();
385 }
386 }
387 KeyCode::Right | KeyCode::Char('l') => lib.switch_view(),
388 KeyCode::Tab => {
389 if lib.view_mode == LibraryViewMode::List {
390 lib.switch_view();
391 } else {
392 lib.switch_view(); }
394 }
395 KeyCode::Char('/') if lib.view_mode == LibraryViewMode::Roms => {
396 lib.enter_search(LibrarySearchMode::Filter);
397 }
398 KeyCode::Char('f') if lib.view_mode == LibraryViewMode::Roms => {
399 lib.enter_search(LibrarySearchMode::Jump);
400 }
401 KeyCode::Enter => {
402 if lib.view_mode == LibraryViewMode::List {
403 lib.switch_view();
404 } else if let Some((primary, others)) = lib.get_selected_group() {
405 let lib_screen = std::mem::replace(
406 &mut self.screen,
407 AppScreen::MainMenu(MainMenuScreen::new()),
408 );
409 if let AppScreen::LibraryBrowse(l) = lib_screen {
410 self.screen = AppScreen::GameDetail(Box::new(GameDetailScreen::new(
411 primary,
412 others,
413 GameDetailPrevious::Library(l),
414 self.downloads.shared(),
415 )));
416 }
417 }
418 }
419 KeyCode::Char('t') => lib.switch_subsection(),
420 KeyCode::Esc => {
421 if lib.view_mode == LibraryViewMode::Roms {
422 lib.back_to_list();
423 } else {
424 self.screen = AppScreen::MainMenu(MainMenuScreen::new());
425 }
426 }
427 KeyCode::Char('q') => return Ok(true),
428 _ => {}
429 }
430 Ok(false)
431 }
432
433 async fn handle_search(&mut self, key: KeyCode) -> Result<bool> {
436 let search = match &mut self.screen {
437 AppScreen::Search(s) => s,
438 _ => return Ok(false),
439 };
440 match key {
441 KeyCode::Backspace => search.delete_char(),
442 KeyCode::Left => search.cursor_left(),
443 KeyCode::Right => search.cursor_right(),
444 KeyCode::Up => search.previous(),
445 KeyCode::Down => search.next(),
446 KeyCode::Char(c) => search.add_char(c),
447 KeyCode::Enter => {
448 if search.result_groups.is_some() {
449 if let Some((primary, others)) = search.get_selected_group() {
450 let prev = std::mem::replace(
451 &mut self.screen,
452 AppScreen::MainMenu(MainMenuScreen::new()),
453 );
454 if let AppScreen::Search(s) = prev {
455 self.screen = AppScreen::GameDetail(Box::new(GameDetailScreen::new(
456 primary,
457 others,
458 GameDetailPrevious::Search(s),
459 self.downloads.shared(),
460 )));
461 }
462 }
463 } else if !search.query.is_empty() {
464 let req = GetRoms {
465 search_term: Some(search.query.clone()),
466 limit: Some(50),
467 ..Default::default()
468 };
469 if let Ok(roms) = self.client.call(&req).await {
470 search.set_results(roms);
471 }
472 }
473 }
474 KeyCode::Esc => {
475 if search.results.is_some() {
476 search.clear_results();
477 } else {
478 self.screen = AppScreen::MainMenu(MainMenuScreen::new());
479 }
480 }
481 _ => {}
482 }
483 Ok(false)
484 }
485
486 fn handle_settings(&mut self, key: KeyCode) -> Result<bool> {
489 let settings = match &mut self.screen {
490 AppScreen::Settings(s) => s,
491 _ => return Ok(false),
492 };
493
494 if settings.editing {
495 match key {
496 KeyCode::Enter => {
497 settings.save_edit();
498 }
499 KeyCode::Esc => settings.cancel_edit(),
500 KeyCode::Backspace => settings.delete_char(),
501 KeyCode::Left => settings.move_cursor_left(),
502 KeyCode::Right => settings.move_cursor_right(),
503 KeyCode::Char(c) => settings.add_char(c),
504 _ => {}
505 }
506 return Ok(false);
507 }
508
509 match key {
510 KeyCode::Up | KeyCode::Char('k') => settings.previous(),
511 KeyCode::Down | KeyCode::Char('j') => settings.next(),
512 KeyCode::Enter => {
513 if settings.selected_index == 3 {
514 self.screen =
515 AppScreen::SetupWizard(Box::new(SetupWizard::new_auth_only(&self.config)));
516 } else {
517 settings.enter_edit();
518 }
519 }
520 KeyCode::Char('s') => {
521 use crate::config::persist_user_config;
523 if let Err(e) = persist_user_config(
524 &settings.base_url,
525 &settings.download_dir,
526 settings.use_https,
527 self.config.auth.clone(),
528 ) {
529 settings.message = Some((format!("Error saving: {e}"), Color::Red));
530 } else {
531 settings.message = Some(("Saved to .env".to_string(), Color::Green));
532 self.config.base_url = settings.base_url.clone();
534 self.config.download_dir = settings.download_dir.clone();
535 self.config.use_https = settings.use_https;
536 if let Ok(new_client) = RommClient::new(&self.config, self.client.verbose()) {
538 self.client = new_client;
539 }
540 }
541 }
542 KeyCode::Esc => self.screen = AppScreen::MainMenu(MainMenuScreen::new()),
543 KeyCode::Char('q') => return Ok(true),
544 _ => {}
545 }
546 Ok(false)
547 }
548
549 fn handle_browse(&mut self, key: KeyCode) -> Result<bool> {
552 use super::screens::browse::ViewMode;
553
554 let browse = match &mut self.screen {
555 AppScreen::Browse(b) => b,
556 _ => return Ok(false),
557 };
558 match key {
559 KeyCode::Up | KeyCode::Char('k') => browse.previous(),
560 KeyCode::Down | KeyCode::Char('j') => browse.next(),
561 KeyCode::Left | KeyCode::Char('h') => {
562 if browse.view_mode == ViewMode::Endpoints {
563 browse.switch_view();
564 }
565 }
566 KeyCode::Right | KeyCode::Char('l') => {
567 if browse.view_mode == ViewMode::Sections {
568 browse.switch_view();
569 }
570 }
571 KeyCode::Tab => browse.switch_view(),
572 KeyCode::Enter => {
573 if browse.view_mode == ViewMode::Endpoints {
574 if let Some(ep) = browse.get_selected_endpoint() {
575 self.screen = AppScreen::Execute(ExecuteScreen::new(ep.clone()));
576 }
577 } else {
578 browse.switch_view();
579 }
580 }
581 KeyCode::Esc => self.screen = AppScreen::MainMenu(MainMenuScreen::new()),
582 _ => {}
583 }
584 Ok(false)
585 }
586
587 async fn handle_execute(&mut self, key: KeyCode) -> Result<bool> {
590 let execute = match &mut self.screen {
591 AppScreen::Execute(e) => e,
592 _ => return Ok(false),
593 };
594 match key {
595 KeyCode::Tab => execute.next_field(),
596 KeyCode::BackTab => execute.previous_field(),
597 KeyCode::Char(c) => execute.add_char_to_focused(c),
598 KeyCode::Backspace => execute.delete_char_from_focused(),
599 KeyCode::Enter => {
600 let endpoint = execute.endpoint.clone();
601 let query = execute.get_query_params();
602 let body = if endpoint.has_body && !execute.body_text.is_empty() {
603 Some(serde_json::from_str(&execute.body_text)?)
604 } else {
605 None
606 };
607 let resolved_path =
608 match resolve_path_template(&endpoint.path, &execute.get_path_params()) {
609 Ok(p) => p,
610 Err(e) => {
611 self.screen = AppScreen::Result(ResultScreen::new(
612 serde_json::json!({ "error": format!("{e}") }),
613 None,
614 None,
615 ));
616 return Ok(false);
617 }
618 };
619 match self
620 .client
621 .request_json(&endpoint.method, &resolved_path, &query, body)
622 .await
623 {
624 Ok(result) => {
625 self.screen = AppScreen::Result(ResultScreen::new(
626 result,
627 Some(&endpoint.method),
628 Some(resolved_path.as_str()),
629 ));
630 }
631 Err(e) => {
632 self.screen = AppScreen::Result(ResultScreen::new(
633 serde_json::json!({ "error": format!("{e}") }),
634 None,
635 None,
636 ));
637 }
638 }
639 }
640 KeyCode::Esc => {
641 self.screen = AppScreen::Browse(BrowseScreen::new(self.registry.clone()));
642 }
643 _ => {}
644 }
645 Ok(false)
646 }
647
648 fn handle_result(&mut self, key: KeyCode) -> Result<bool> {
651 use super::screens::result::ResultViewMode;
652
653 let result = match &mut self.screen {
654 AppScreen::Result(r) => r,
655 _ => return Ok(false),
656 };
657 match key {
658 KeyCode::Up | KeyCode::Char('k') => {
659 if result.view_mode == ResultViewMode::Json {
660 result.scroll_up(1);
661 } else {
662 result.table_previous();
663 }
664 }
665 KeyCode::Down => {
666 if result.view_mode == ResultViewMode::Json {
667 result.scroll_down(1);
668 } else {
669 result.table_next();
670 }
671 }
672 KeyCode::Char('j') => {
673 if result.view_mode == ResultViewMode::Json {
674 result.scroll_down(1);
675 }
676 }
677 KeyCode::PageUp => {
678 if result.view_mode == ResultViewMode::Table {
679 result.table_page_up();
680 } else {
681 result.scroll_up(10);
682 }
683 }
684 KeyCode::PageDown => {
685 if result.view_mode == ResultViewMode::Table {
686 result.table_page_down();
687 } else {
688 result.scroll_down(10);
689 }
690 }
691 KeyCode::Char('t') => {
692 if result.table_row_count > 0 {
693 result.switch_view_mode();
694 }
695 }
696 KeyCode::Enter => {
697 if result.view_mode == ResultViewMode::Table && result.table_row_count > 0 {
698 if let Some(item) = result.get_selected_item_value() {
699 let prev = std::mem::replace(
700 &mut self.screen,
701 AppScreen::MainMenu(MainMenuScreen::new()),
702 );
703 if let AppScreen::Result(rs) = prev {
704 self.screen =
705 AppScreen::ResultDetail(ResultDetailScreen::new(rs, item));
706 }
707 }
708 }
709 }
710 KeyCode::Esc => {
711 result.clear_message();
712 self.screen = AppScreen::Browse(BrowseScreen::new(self.registry.clone()));
713 }
714 KeyCode::Char('q') => return Ok(true),
715 _ => {}
716 }
717 Ok(false)
718 }
719
720 fn handle_result_detail(&mut self, key: KeyCode) -> Result<bool> {
723 let detail = match &mut self.screen {
724 AppScreen::ResultDetail(d) => d,
725 _ => return Ok(false),
726 };
727 match key {
728 KeyCode::Up | KeyCode::Char('k') => detail.scroll_up(1),
729 KeyCode::Down | KeyCode::Char('j') => detail.scroll_down(1),
730 KeyCode::PageUp => detail.scroll_up(10),
731 KeyCode::PageDown => detail.scroll_down(10),
732 KeyCode::Char('o') => detail.open_image_url(),
733 KeyCode::Esc => {
734 detail.clear_message();
735 let prev =
736 std::mem::replace(&mut self.screen, AppScreen::MainMenu(MainMenuScreen::new()));
737 if let AppScreen::ResultDetail(d) = prev {
738 self.screen = AppScreen::Result(d.parent);
739 }
740 }
741 KeyCode::Char('q') => return Ok(true),
742 _ => {}
743 }
744 Ok(false)
745 }
746
747 fn handle_game_detail(&mut self, key: KeyCode) -> Result<bool> {
750 let detail = match &mut self.screen {
751 AppScreen::GameDetail(d) => d,
752 _ => return Ok(false),
753 };
754
755 if !detail.download_completion_acknowledged {
758 if let Ok(list) = detail.downloads.lock() {
759 let has_completed = list.iter().any(|j| {
760 j.rom_id == detail.rom.id
761 && matches!(
762 j.status,
763 crate::core::download::DownloadStatus::Done
764 | crate::core::download::DownloadStatus::Error(_)
765 )
766 });
767 let is_still_downloading = list.iter().any(|j| {
768 j.rom_id == detail.rom.id
769 && matches!(j.status, crate::core::download::DownloadStatus::Downloading)
770 });
771 if has_completed && !is_still_downloading {
773 detail.download_completion_acknowledged = true;
774 }
775 }
776 }
777
778 match key {
779 KeyCode::Enter => {
780 if !detail.has_started_download {
783 detail.has_started_download = true;
784 self.downloads
785 .start_download(&detail.rom, self.client.clone());
786 }
787 }
788 KeyCode::Char('o') => detail.open_cover(),
789 KeyCode::Char('m') => detail.toggle_technical(),
790 KeyCode::Esc => {
791 detail.clear_message();
792 let prev =
793 std::mem::replace(&mut self.screen, AppScreen::MainMenu(MainMenuScreen::new()));
794 if let AppScreen::GameDetail(g) = prev {
795 self.screen = match g.previous {
796 GameDetailPrevious::Library(l) => AppScreen::LibraryBrowse(l),
797 GameDetailPrevious::Search(s) => AppScreen::Search(s),
798 };
799 }
800 }
801 KeyCode::Char('q') => return Ok(true),
802 _ => {}
803 }
804 Ok(false)
805 }
806
807 async fn handle_setup_wizard(&mut self, key: KeyCode) -> Result<bool> {
810 let wizard = match &mut self.screen {
811 AppScreen::SetupWizard(w) => w,
812 _ => return Ok(false),
813 };
814
815 let event = crossterm::event::KeyEvent::new(key, crossterm::event::KeyModifiers::empty());
817 if wizard.handle_key(event)? {
818 self.screen = AppScreen::Settings(SettingsScreen::new(
820 &self.config,
821 self.server_version.as_deref(),
822 ));
823 return Ok(false);
824 }
825
826 if wizard.testing {
827 let result = wizard.try_connect_and_persist(self.client.verbose()).await;
828 wizard.testing = false;
829 match result {
830 Ok(cfg) => {
831 self.config = cfg;
832 if let Ok(new_client) = RommClient::new(&self.config, self.client.verbose()) {
833 self.client = new_client;
834 }
835 let mut settings =
836 SettingsScreen::new(&self.config, self.server_version.as_deref());
837 settings.message = Some((
838 "Authentication updated successfully".to_string(),
839 Color::Green,
840 ));
841 self.screen = AppScreen::Settings(settings);
842 }
843 Err(e) => {
844 wizard.error = Some(format!("{e:#}"));
845 }
846 }
847 }
848 Ok(false)
849 }
850
851 fn render(&mut self, f: &mut ratatui::Frame) {
856 let area = f.size();
857 if let Some(ref splash) = self.startup_splash {
858 connected_splash::render(f, area, splash);
859 return;
860 }
861 match &mut self.screen {
862 AppScreen::MainMenu(menu) => menu.render(f, area),
863 AppScreen::LibraryBrowse(lib) => lib.render(f, area),
864 AppScreen::Search(search) => {
865 search.render(f, area);
866 if let Some((x, y)) = search.cursor_position(area) {
867 f.set_cursor(x, y);
868 }
869 }
870 AppScreen::Settings(settings) => {
871 settings.render(f, area);
872 if let Some((x, y)) = settings.cursor_position(area) {
873 f.set_cursor(x, y);
874 }
875 }
876 AppScreen::Browse(browse) => browse.render(f, area),
877 AppScreen::Execute(execute) => {
878 execute.render(f, area);
879 if let Some((x, y)) = execute.cursor_position(area) {
880 f.set_cursor(x, y);
881 }
882 }
883 AppScreen::Result(result) => result.render(f, area),
884 AppScreen::ResultDetail(detail) => detail.render(f, area),
885 AppScreen::GameDetail(detail) => detail.render(f, area),
886 AppScreen::Download(d) => d.render(f, area),
887 AppScreen::SetupWizard(wizard) => {
888 wizard.render(f, area);
889 if let Some((x, y)) = wizard.cursor_pos(area) {
890 f.set_cursor(x, y);
891 }
892 }
893 }
894 }
895}