From 70678432c6b219c0476cdc5cb458e4bd56d21dc0 Mon Sep 17 00:00:00 2001 From: filipriec Date: Mon, 7 Apr 2025 21:27:01 +0200 Subject: [PATCH] BREAKING CHANGES updating gRPC based on the enum now --- client/src/functions/modes/edit/form_e.rs | 49 +++-- client/src/modes/canvas/common_mode.rs | 38 ++-- client/src/modes/common/command_mode.rs | 52 ++--- client/src/modes/handlers/event.rs | 79 ++++---- client/src/services/ui_service.rs | 23 +++ client/src/tui/functions/common/form.rs | 27 ++- client/src/ui/handlers/ui.rs | 230 ++++++++++++++-------- 7 files changed, 316 insertions(+), 182 deletions(-) diff --git a/client/src/functions/modes/edit/form_e.rs b/client/src/functions/modes/edit/form_e.rs index efb12ec..9a54397 100644 --- a/client/src/functions/modes/edit/form_e.rs +++ b/client/src/functions/modes/edit/form_e.rs @@ -4,6 +4,8 @@ use crate::services::grpc_client::GrpcClient; use crate::state::canvas_state::CanvasState; use crate::state::pages::form::FormState; use crate::tui::functions::common::form::{revert, save}; +use crate::tui::functions::common::form::SaveOutcome; +use crate::modes::handlers::event::EventOutcome; use crossterm::event::{KeyCode, KeyEvent}; use std::any::Any; @@ -11,48 +13,61 @@ pub async fn execute_common_action( action: &str, state: &mut S, grpc_client: &mut GrpcClient, - is_saved: &mut bool, current_position: &mut u64, total_count: u64, -) -> Result> { +) -> Result> { match action { "save" | "revert" => { if !state.has_unsaved_changes() { - return Ok("No changes to save or revert.".to_string()); + return Ok(EventOutcome::Ok("No changes to save or revert.".to_string())); } if let Some(form_state) = (state as &mut dyn Any).downcast_mut::() { match action { "save" => { - save( + let save_result = save( form_state, grpc_client, - is_saved, current_position, total_count, - ) - .await + ).await; + + match save_result { + Ok(save_outcome) => { + let message = match save_outcome { + SaveOutcome::NoChange => "No changes to save.".to_string(), + SaveOutcome::UpdatedExisting => "Entry updated.".to_string(), + SaveOutcome::CreatedNew(_) => "New entry created.".to_string(), + }; + Ok(EventOutcome::DataSaved(save_outcome, message)) + } + Err(e) => Err(e), + } } "revert" => { - revert( + let revert_result = revert( form_state, grpc_client, current_position, total_count, - ) - .await + ).await; + + match revert_result { + Ok(message) => Ok(EventOutcome::Ok(message)), + Err(e) => Err(e), + } } _ => unreachable!(), } } else { - Ok(format!( + Ok(EventOutcome::Ok(format!( "Action '{}' not implemented for this state type.", action - )) + ))) } } - _ => Ok(format!("Common action '{}' not handled here.", action)), + _ => Ok(EventOutcome::Ok(format!("Common action '{}' not handled here.", action))), } } @@ -61,10 +76,10 @@ pub async fn execute_edit_action( key: KeyEvent, state: &mut S, ideal_cursor_column: &mut usize, - _grpc_client: &mut GrpcClient, - _is_saved: &mut bool, - _current_position: &mut u64, - _total_count: u64, + grpc_client: &mut GrpcClient, + is_saved: &mut bool, + current_position: &mut u64, + total_count: u64, ) -> Result> { match action { "insert_char" => { diff --git a/client/src/modes/canvas/common_mode.rs b/client/src/modes/canvas/common_mode.rs index 985bbfd..41f44e5 100644 --- a/client/src/modes/canvas/common_mode.rs +++ b/client/src/modes/canvas/common_mode.rs @@ -1,10 +1,12 @@ -// src/modes/canvas/common.rs +// src/modes/canvas/common_mode.rs use crate::tui::terminal::core::TerminalCore; use crate::state::pages::{form::FormState, auth::AuthState}; use crate::state::state::AppState; use crate::services::grpc_client::GrpcClient; use crate::services::auth::AuthClient; +use crate::modes::handlers::event::EventOutcome; +use crate::tui::functions::common::form::SaveOutcome; use crate::tui::functions::common::{ form::{save as form_save, revert as form_revert}, login::{save as login_save, revert as login_revert} @@ -20,46 +22,54 @@ pub async fn handle_core_action( app_state: &mut AppState, current_position: &mut u64, total_count: u64, -) -> Result<(bool, String), Box> { +) -> Result> { match action { "save" => { if app_state.ui.show_login { let message = login_save(auth_state, auth_client, app_state).await?; - Ok((false, message)) + Ok(EventOutcome::Ok(message)) } else { - let message = form_save( + let save_outcome = form_save( form_state, grpc_client, - &mut app_state.ui.is_saved, current_position, total_count, ).await?; - Ok((false, message)) + let message = match save_outcome { + SaveOutcome::NoChange => "No changes to save.".to_string(), + SaveOutcome::UpdatedExisting => "Entry updated.".to_string(), + SaveOutcome::CreatedNew(_) => "New entry created.".to_string(), + }; + Ok(EventOutcome::DataSaved(save_outcome, message)) } }, "force_quit" => { terminal.cleanup()?; - Ok((true, "Force exiting without saving.".to_string())) + Ok(EventOutcome::Exit("Force exiting without saving.".to_string())) }, "save_and_quit" => { let message = if app_state.ui.show_login { login_save(auth_state, auth_client, app_state).await? } else { - form_save( + let save_outcome = form_save( form_state, grpc_client, - &mut app_state.ui.is_saved, current_position, total_count, - ).await? + ).await?; + match save_outcome { + SaveOutcome::NoChange => "No changes to save.".to_string(), + SaveOutcome::UpdatedExisting => "Entry updated.".to_string(), + SaveOutcome::CreatedNew(_) => "New entry created.".to_string(), + } }; terminal.cleanup()?; - Ok((true, format!("{}. Exiting application.", message))) + Ok(EventOutcome::Exit(format!("{}. Exiting application.", message))) }, "revert" => { if app_state.ui.show_login { let message = login_revert(auth_state, app_state).await; - Ok((false, message)) + Ok(EventOutcome::Ok(message)) } else { let message = form_revert( form_state, @@ -67,9 +77,9 @@ pub async fn handle_core_action( current_position, total_count, ).await?; - Ok((false, message)) + Ok(EventOutcome::Ok(message)) } }, - _ => Ok((false, format!("Core action not handled: {}", action))), + _ => Ok(EventOutcome::Ok(format!("Core action not handled: {}", action))), } } diff --git a/client/src/modes/common/command_mode.rs b/client/src/modes/common/command_mode.rs index 2b208d6..ca5a609 100644 --- a/client/src/modes/common/command_mode.rs +++ b/client/src/modes/common/command_mode.rs @@ -1,4 +1,4 @@ -// src/modes/handlers/command_mode.rs +// src/modes/common/command_mode.rs use crossterm::event::{KeyEvent, KeyCode, KeyModifiers}; use crate::config::binds::config::Config; @@ -7,6 +7,14 @@ use crate::state::pages::form::FormState; use crate::modes::common::commands::CommandHandler; use crate::tui::terminal::core::TerminalCore; use crate::tui::functions::common::form::{save, revert}; +use crate::modes::handlers::event::EventOutcome; +use crate::tui::functions::common::form::SaveOutcome; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum CommandOutcome { + ExitCommandMode(String), + // Other command-specific outcomes if needed +} pub async fn handle_command_event( key: KeyEvent, @@ -19,15 +27,12 @@ pub async fn handle_command_event( terminal: &mut TerminalCore, current_position: &mut u64, total_count: u64, -) -> Result<(bool, String, bool), Box> { - - // Return value: (should_exit, message, should_exit_command_mode) - +) -> Result> { // Exit command mode (via configurable keybinding) if config.is_exit_command_mode(key.code, key.modifiers) { command_input.clear(); *command_message = "".to_string(); - return Ok((false, "".to_string(), true)); + return Ok(EventOutcome::Ok("Exited command mode".to_string())); } // Execute command (via configurable keybinding, defaults to Enter) @@ -48,7 +53,7 @@ pub async fn handle_command_event( // Backspace (via configurable keybinding, defaults to Backspace) if config.is_command_backspace(key.code, key.modifiers) { command_input.pop(); - return Ok((false, "".to_string(), false)); + return Ok(EventOutcome::Ok("".to_string())); } // Regular character input - accept any character in command mode @@ -56,12 +61,12 @@ pub async fn handle_command_event( // Accept regular or shifted characters (e.g., 'a' or 'A') if key.modifiers.is_empty() || key.modifiers == KeyModifiers::SHIFT { command_input.push(c); - return Ok((false, "".to_string(), false)); + return Ok(EventOutcome::Ok("".to_string())); } } // Ignore all other keys - Ok((false, "".to_string(), false)) + Ok(EventOutcome::Ok("".to_string())) } async fn process_command( @@ -74,12 +79,12 @@ async fn process_command( terminal: &mut TerminalCore, current_position: &mut u64, total_count: u64, -) -> Result<(bool, String, bool), Box> { +) -> Result> { // Clone the trimmed command to avoid borrow issues let command = command_input.trim().to_string(); if command.is_empty() { *command_message = "Empty command".to_string(); - return Ok((false, command_message.clone(), false)); + return Ok(EventOutcome::Ok(command_message.clone())); } // Get the action for the command (now checks global and common bindings too) @@ -92,18 +97,26 @@ async fn process_command( .handle_command(action, terminal) .await?; command_input.clear(); - Ok((should_exit, message, true)) + if should_exit { + Ok(EventOutcome::Exit(message)) + } else { + Ok(EventOutcome::Ok(message)) + } }, "save" => { - let message = save( + let outcome = save( form_state, grpc_client, - &mut command_handler.is_saved, current_position, total_count, ).await?; + let message = match outcome { + SaveOutcome::CreatedNew(_) => "New entry created".to_string(), + SaveOutcome::UpdatedExisting => "Entry updated".to_string(), + SaveOutcome::NoChange => "No changes to save".to_string(), + }; command_input.clear(); - return Ok((false, message, true)); + Ok(EventOutcome::DataSaved(outcome, message)) }, "revert" => { let message = revert( @@ -113,17 +126,12 @@ async fn process_command( total_count, ).await?; command_input.clear(); - return Ok((false, message, true)); - }, - "unknown" => { - let message = format!("Unknown command: {}", command); - command_input.clear(); - return Ok((false, message, true)); + Ok(EventOutcome::Ok(message)) }, _ => { let message = format!("Unhandled action: {}", action); command_input.clear(); - return Ok((false, message, true)); + Ok(EventOutcome::Ok(message)) } } } diff --git a/client/src/modes/handlers/event.rs b/client/src/modes/handlers/event.rs index 697c3ca..fcb95a1 100644 --- a/client/src/modes/handlers/event.rs +++ b/client/src/modes/handlers/event.rs @@ -17,6 +17,15 @@ use crate::modes::{ }; use crate::config::binds::key_sequences::KeySequenceTracker; use crate::modes::handlers::mode_manager::{ModeManager, AppMode}; +use crate::tui::functions::common::form::SaveOutcome; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum EventOutcome { + Ok(String), // Normal operation, display message + Exit(String), // Signal app exit, display message + DataSaved(SaveOutcome, String), // Data save attempted, include outcome and message + // Add other outcomes like QuitRequested, SaveAndQuitRequested later if needed +} pub struct EventHandler { pub command_mode: bool, @@ -55,7 +64,7 @@ impl EventHandler { app_state: &mut crate::state::state::AppState, total_count: u64, current_position: &mut u64, - ) -> Result<(bool, String), Box> { + ) -> Result> { let current_mode = ModeManager::derive_mode(app_state, self); app_state.update_mode(current_mode); @@ -64,9 +73,10 @@ impl EventHandler { let modifiers = key.modifiers; if UiStateHandler::toggle_sidebar(&mut app_state.ui, config, key_code, modifiers) { - return Ok((false, format!("Sidebar {}", + let message = format!("Sidebar {}", if app_state.ui.show_sidebar { "shown" } else { "hidden" } - ))); + ); + return Ok(EventOutcome::Ok(message)); } match current_mode { @@ -90,7 +100,7 @@ impl EventHandler { self.edit_mode_cooldown = true; self.command_message = "Edit mode".to_string(); terminal.set_cursor_style(SetCursorStyle::BlinkingBar)?; - return Ok((false, self.command_message.clone())); + return Ok(EventOutcome::Ok(self.command_message.clone())); } if config.is_enter_edit_mode_after(key_code, modifiers) && @@ -119,7 +129,7 @@ impl EventHandler { self.edit_mode_cooldown = true; self.command_message = "Edit mode (after cursor)".to_string(); terminal.set_cursor_style(SetCursorStyle::BlinkingBar)?; - return Ok((false, self.command_message.clone())); + return Ok(EventOutcome::Ok(self.command_message.clone())); } if let Some(action) = config.get_read_only_action_for_key(key_code, modifiers) { @@ -127,7 +137,7 @@ impl EventHandler { self.command_mode = true; self.command_input.clear(); self.command_message.clear(); - return Ok((false, String::new())); + return Ok(EventOutcome::Ok(String::new())); } } @@ -154,7 +164,7 @@ impl EventHandler { } } - return read_only::handle_read_only_event( + let message = read_only::handle_read_only_event( app_state, key, config, @@ -167,26 +177,27 @@ impl EventHandler { &mut self.command_message, &mut self.edit_mode_cooldown, &mut self.ideal_cursor_column, - ).await; + ).await?; + return Ok(EventOutcome::Ok(message)); }, AppMode::Edit => { if config.is_exit_edit_mode(key_code, modifiers) { self.is_edit_mode = false; self.edit_mode_cooldown = true; - + let has_changes = if app_state.ui.show_login { auth_state.has_unsaved_changes() } else { form_state.has_unsaved_changes() }; - + self.command_message = if has_changes { "Exited edit mode (unsaved changes remain)".to_string() } else { "Read-only mode".to_string() }; - + terminal.set_cursor_style(SetCursorStyle::SteadyBlock)?; let current_input = if app_state.ui.show_login { @@ -210,7 +221,7 @@ impl EventHandler { self.ideal_cursor_column = form_state.current_cursor_pos(); } } - return Ok((false, self.command_message.clone())); + return Ok(EventOutcome::Ok(self.command_message.clone())); } if let Some(action) = config.get_action_for_key_in_mode( @@ -220,23 +231,23 @@ impl EventHandler { ) { match action { "save" | "force_quit" | "save_and_quit" | "revert" => { - return common_mode::handle_core_action( - action, - form_state, - auth_state, - grpc_client, - &mut self.auth_client, - terminal, - app_state, - current_position, - total_count, - ).await; - }, + return common_mode::handle_core_action( + action, + form_state, + auth_state, + grpc_client, + &mut self.auth_client, + terminal, + app_state, + current_position, + total_count, + ).await; + }, _ => {} } } - let result = edit::handle_edit_event( + let message = edit::handle_edit_event( app_state.ui.show_login, key, config, @@ -244,18 +255,17 @@ impl EventHandler { auth_state, &mut self.ideal_cursor_column, &mut self.command_message, - &mut app_state.ui.is_saved, current_position, total_count, grpc_client, ).await?; self.key_sequence_tracker.reset(); - return Ok((false, result)); + return Ok(EventOutcome::Ok(message)); }, AppMode::Command => { - let (should_exit, message, exit_command_mode) = command_mode::handle_command_event( + let outcome = command_mode::handle_command_event( key, config, form_state, @@ -267,17 +277,18 @@ impl EventHandler { current_position, total_count, ).await?; - - if exit_command_mode { - self.command_mode = false; + + if let EventOutcome::Ok(msg) = &outcome { + if msg == "Exited command mode" { + self.command_mode = false; + } } - - return Ok((should_exit, message)); + return Ok(outcome); } } } self.edit_mode_cooldown = false; - Ok((false, self.command_message.clone())) + Ok(EventOutcome::Ok(self.command_message.clone())) } } diff --git a/client/src/services/ui_service.rs b/client/src/services/ui_service.rs index c43aee5..999bf84 100644 --- a/client/src/services/ui_service.rs +++ b/client/src/services/ui_service.rs @@ -2,6 +2,7 @@ use crate::services::grpc_client::GrpcClient; use crate::state::pages::form::FormState; +use crate::tui::functions::common::form::SaveOutcome; use crate::state::state::AppState; pub struct UiService; @@ -85,5 +86,27 @@ impl UiService { } } } + + /// Handles the consequences of a save operation, like updating counts. + pub async fn handle_save_outcome( + save_outcome: SaveOutcome, + grpc_client: &mut GrpcClient, + app_state: &mut AppState, + form_state: &mut FormState, // Needed to potentially update position/ID + ) -> Result<(), Box> { + match save_outcome { + SaveOutcome::CreatedNew(new_id) => { + // A new record was created, update the count! + UiService::update_adresar_count(grpc_client, app_state).await?; + // Navigate to the new record (now that count is updated) + app_state.update_current_position(app_state.total_count); + form_state.id = new_id; // Ensure ID is set (might be redundant if save already did it) + } + SaveOutcome::UpdatedExisting | SaveOutcome::NoChange => { + // No count update needed for these outcomes + } + } + Ok(()) + } } diff --git a/client/src/tui/functions/common/form.rs b/client/src/tui/functions/common/form.rs index 44c4969..9260d66 100644 --- a/client/src/tui/functions/common/form.rs +++ b/client/src/tui/functions/common/form.rs @@ -4,17 +4,26 @@ use crate::services::grpc_client::GrpcClient; use crate::state::pages::form::FormState; use common::proto::multieko2::adresar::{PostAdresarRequest, PutAdresarRequest}; +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum SaveOutcome { + NoChange, // Nothing needed saving + UpdatedExisting, // An existing record was updated + CreatedNew(i64), // A new record was created (include its new ID) +} + /// Shared logic for saving the current form state pub async fn save( form_state: &mut FormState, grpc_client: &mut GrpcClient, - is_saved: &mut bool, current_position: &mut u64, total_count: u64, -) -> Result> { +) -> Result> { // <-- Return SaveOutcome + if !form_state.has_unsaved_changes { + return Ok(SaveOutcome::NoChange); // Early exit if no changes + } let is_new = *current_position == total_count + 1; - let message = if is_new { + let outcome = if is_new { let post_request = PostAdresarRequest { firma: form_state.values[0].clone(), kz: form_state.values[1].clone(), @@ -33,10 +42,9 @@ pub async fn save( fax: form_state.values[14].clone(), }; let response = grpc_client.post_adresar(post_request).await?; - let new_total = grpc_client.get_adresar_count().await?; - *current_position = new_total; - form_state.id = response.into_inner().id; - "New entry created".to_string() + let new_id = response.into_inner().id; + form_state.id = new_id; + SaveOutcome::CreatedNew(new_id) // <-- Return CreatedNew with ID } else { let put_request = PutAdresarRequest { id: form_state.id, @@ -57,12 +65,11 @@ pub async fn save( fax: form_state.values[14].clone(), }; let _ = grpc_client.put_adresar(put_request).await?; - "Entry updated".to_string() + SaveOutcome::UpdatedExisting }; - *is_saved = true; form_state.has_unsaved_changes = false; - Ok(message) + Ok(outcome) } /// Discard changes since last save diff --git a/client/src/ui/handlers/ui.rs b/client/src/ui/handlers/ui.rs index 8257f85..ec687b6 100644 --- a/client/src/ui/handlers/ui.rs +++ b/client/src/ui/handlers/ui.rs @@ -1,26 +1,25 @@ // src/ui/handlers/ui.rs -use crate::tui::terminal::TerminalCore; +use crate::config::binds::config::Config; +use crate::config::colors::themes::Theme; +use crate::modes::common::commands::CommandHandler; +use crate::modes::handlers::event::{EventHandler, EventOutcome}; // Import EventOutcome +use crate::modes::handlers::mode_manager::{AppMode, ModeManager}; use crate::services::grpc_client::GrpcClient; use crate::services::ui_service::UiService; -use crate::tui::terminal::EventReader; -use crate::modes::common::commands::CommandHandler; -use crate::modes::handlers::mode_manager::{AppMode, ModeManager}; -use crate::config::colors::themes::Theme; -use crate::config::binds::config::Config; -use crate::ui::handlers::render::render_ui; -use crate::state::pages::form::FormState; -use crate::state::pages::auth::AuthState; use crate::state::canvas_state::CanvasState; -use crate::modes::handlers::event::EventHandler; +use crate::state::pages::auth::AuthState; +use crate::state::pages::form::FormState; use crate::state::state::AppState; +use crate::tui::functions::common::form::SaveOutcome; // Import SaveOutcome +use crate::tui::terminal::{EventReader, TerminalCore}; +use crate::ui::handlers::render::render_ui; use crossterm::cursor::SetCursorStyle; pub async fn run_ui() -> Result<(), Box> { let config = Config::load()?; let mut terminal = TerminalCore::new()?; let mut grpc_client = GrpcClient::new().await?; - // let auth_client = AuthClient::new().await?; // AuthClient is now inside EventHandler let mut command_handler = CommandHandler::new(); let theme = Theme::from_str(&config.colors.theme); let mut auth_state = AuthState::default(); // The single source of truth for AuthState @@ -29,7 +28,9 @@ pub async fn run_ui() -> Result<(), Box> { let mut app_state = AppState::new()?; // Initialize app state with profile tree and table structure - let column_names = UiService::initialize_app_state(&mut grpc_client, &mut app_state).await?; + let column_names = + UiService::initialize_app_state(&mut grpc_client, &mut app_state) + .await?; // Initialize FormState with dynamic fields let mut form_state = FormState::new(column_names); @@ -39,7 +40,8 @@ pub async fn run_ui() -> Result<(), Box> { let event_reader = EventReader::new(); // Fetch the total count of Adresar entries - UiService::initialize_adresar_count(&mut grpc_client, &mut app_state).await?; + UiService::initialize_adresar_count(&mut grpc_client, &mut app_state) + .await?; form_state.reset_to_empty(); loop { @@ -63,97 +65,158 @@ pub async fn run_ui() -> Result<(), Box> { ); })?; - // --- Cursor Visibility Logic --- - let current_mode = ModeManager::derive_mode(&app_state, &event_handler); - match current_mode { - AppMode::Edit => { - terminal.show_cursor()?; - } - AppMode::ReadOnly => { - if !app_state.ui.focus_outside_canvas { - terminal.set_cursor_style(SetCursorStyle::SteadyBlock)?; - } else { - terminal.set_cursor_style(SetCursorStyle::SteadyUnderScore)?; - } - terminal.show_cursor()?; // Ensure visible - } - AppMode::General | AppMode::Command => { - terminal.set_cursor_style(SetCursorStyle::SteadyUnderScore)?; - terminal.show_cursor()?; // Ensure visible (though might not be positioned meaningfully) - } - } - // --- End Cursor Visibility Logic --- - - let total_count = app_state.total_count; + // --- Cursor Visibility Logic --- + let current_mode = ModeManager::derive_mode(&app_state, &event_handler); + match current_mode { + AppMode::Edit => { + terminal.show_cursor()?; + } + AppMode::ReadOnly => { + if !app_state.ui.focus_outside_canvas { + terminal.set_cursor_style(SetCursorStyle::SteadyBlock)?; + } else { + terminal + .set_cursor_style(SetCursorStyle::SteadyUnderScore)?; + } + terminal.show_cursor()?; // Ensure visible + } + AppMode::General | AppMode::Command => { + terminal.set_cursor_style(SetCursorStyle::SteadyUnderScore)?; + terminal.show_cursor()?; // Ensure visible (though might not be positioned meaningfully) + } + } + // --- End Cursor Visibility Logic --- + + let total_count = app_state.total_count; // Keep track for save logic let mut current_position = app_state.current_position; - // Store position before event handling to detect navigation let position_before_event = current_position; let event = event_reader.read_event()?; - let (should_exit, message) = event_handler.handle_event( - event, - &config, - &mut terminal, - &mut grpc_client, - &mut command_handler, - &mut form_state, - &mut auth_state, // Pass the single AuthState instance here too - &mut app_state, - total_count, - &mut current_position, - ).await?; + // Get the outcome from the event handler + let event_outcome_result = event_handler + .handle_event( + event, + &config, + &mut terminal, // Pass terminal mutably + &mut grpc_client, + &mut command_handler, + &mut form_state, + &mut auth_state, + &mut app_state, + total_count, // Pass the count *before* potential save + &mut current_position, + ) + .await; + + // Update position based on handler's modification app_state.current_position = current_position; - let position_changed = app_state.current_position != position_before_event; + // --- Centralized Consequence Handling --- + let mut should_exit = false; + match event_outcome_result { + // Handle the Result first + Ok(outcome) => match outcome { + // Handle the Ok variant containing EventOutcome + EventOutcome::Ok(message) => { + if !message.is_empty() { + event_handler.command_message = message; + } + } + EventOutcome::Exit(message) => { + event_handler.command_message = message; + should_exit = true; + } + EventOutcome::DataSaved(save_outcome, message) => { + event_handler.command_message = message; // Show save status + + // *** Delegate outcome handling to UiService *** + if let Err(e) = UiService::handle_save_outcome( + save_outcome, + &mut grpc_client, + &mut app_state, + &mut form_state, + ) + .await + { + // Handle potential errors from the outcome handler itself + event_handler.command_message = + format!("Error handling save outcome: {}", e); + } + // No count update needed for UpdatedExisting or NoChange + } + }, + Err(e) => { + // Handle errors from handle_event, e.g., log or display + event_handler.command_message = format!("Error: {}", e); + // Decide if the error is fatal, maybe set should_exit = true; + } + } + + // --- Position Change Handling (after outcome processing) --- + let position_changed = + app_state.current_position != position_before_event; // Calculate after potential update + // Recalculate total_count *after* potential update + let current_total_count = app_state.total_count; // Handle position changes and update form state (Only when form is shown) if app_state.ui.show_form { if position_changed && !event_handler.is_edit_mode { let current_input = form_state.get_current_input(); let max_cursor_pos = if !current_input.is_empty() { - current_input.len() - 1 // Limit to last character in readonly mode + current_input.len() - 1 // Limit to last character in readonly mode } else { 0 }; - form_state.current_cursor_pos = event_handler.ideal_cursor_column.min(max_cursor_pos); + form_state.current_cursor_pos = + event_handler.ideal_cursor_column.min(max_cursor_pos); // Ensure position never exceeds total_count + 1 - if app_state.current_position > total_count + 1 { - app_state.current_position = total_count + 1; + if app_state.current_position > current_total_count + 1 { + app_state.current_position = current_total_count + 1; } - if app_state.current_position > total_count { + if app_state.current_position > current_total_count { // New entry - reset form form_state.reset_to_empty(); form_state.current_field = 0; - } else if app_state.current_position >= 1 && app_state.current_position <= total_count { + } else if app_state.current_position >= 1 + && app_state.current_position <= current_total_count + { // Existing entry - load data let current_position_to_load = app_state.current_position; // Use a copy let load_message = UiService::load_adresar_by_position( &mut grpc_client, &mut app_state, // Pass app_state mutably if needed by the service &mut form_state, - current_position_to_load - ).await?; + current_position_to_load, + ) + .await?; let current_input = form_state.get_current_input(); - let max_cursor_pos = if !event_handler.is_edit_mode && !current_input.is_empty() { - current_input.len() - 1 // In readonly mode, limit to last character + let max_cursor_pos = if !event_handler.is_edit_mode + && !current_input.is_empty() + { + current_input.len() - 1 // In readonly mode, limit to last character } else { current_input.len() }; - form_state.current_cursor_pos = event_handler.ideal_cursor_column.min(max_cursor_pos); + form_state.current_cursor_pos = event_handler + .ideal_cursor_column + .min(max_cursor_pos); // Don't overwrite message from handle_event if load_message is simple success - if !load_message.starts_with("Loaded entry") || message.is_empty() { - event_handler.command_message = load_message; + if !load_message.starts_with("Loaded entry") + || event_handler.command_message.is_empty() + { + event_handler.command_message = load_message; } } else { // Invalid position (e.g., 0) - reset to first entry or new entry mode - app_state.current_position = 1.min(total_count + 1); // Go to 1 or new entry if empty - if app_state.current_position > total_count { - form_state.reset_to_empty(); - form_state.current_field = 0; - } + app_state.current_position = + 1.min(current_total_count + 1); // Go to 1 or new entry if empty + if app_state.current_position > total_count { + form_state.reset_to_empty(); + form_state.current_field = 0; + } } } else if !position_changed && !event_handler.is_edit_mode { // If position didn't change but we are in read-only, just adjust cursor @@ -163,29 +226,26 @@ pub async fn run_ui() -> Result<(), Box> { } else { 0 }; - form_state.current_cursor_pos = event_handler.ideal_cursor_column.min(max_cursor_pos); - + form_state.current_cursor_pos = + event_handler.ideal_cursor_column.min(max_cursor_pos); } } else if app_state.ui.show_login { - // Handle cursor updates for AuthState if needed, similar to FormState - if !event_handler.is_edit_mode { - let current_input = auth_state.get_current_input(); - let max_cursor_pos = if !current_input.is_empty() { - current_input.len() - 1 - } else { - 0 - }; - auth_state.current_cursor_pos = event_handler.ideal_cursor_column.min(max_cursor_pos); - } - } - - - // Only update command message if handle_event provided one - if !message.is_empty() { - event_handler.command_message = message; + // Handle cursor updates for AuthState if needed, similar to FormState + if !event_handler.is_edit_mode { + let current_input = auth_state.get_current_input(); + let max_cursor_pos = if !current_input.is_empty() { + current_input.len() - 1 + } else { + 0 + }; + auth_state.current_cursor_pos = + event_handler.ideal_cursor_column.min(max_cursor_pos); + } } + // Check exit condition *after* processing outcome if should_exit { + // terminal.cleanup()?; // Optional: Drop handles this return Ok(()); } }