rust_kanban/app/
mod.rs

1use crate::{
2    app::{
3        actions::Action,
4        app_helper::{
5            handle_edit_keybinding_mode, handle_general_actions, handle_mouse_action,
6            handle_user_input_mode, prepare_config_for_new_app,
7        },
8        kanban::{Board, Boards, Card, CardPriority, CardStatus},
9        state::{AppStatus, Focus, KeyBindingEnum, KeyBindings},
10    },
11    constants::{
12        DEFAULT_CARD_WARNING_DUE_DATE_DAYS, DEFAULT_NO_OF_BOARDS_PER_PAGE,
13        DEFAULT_NO_OF_CARDS_PER_BOARD, DEFAULT_TICKRATE, DEFAULT_TOAST_DURATION, DEFAULT_VIEW,
14        FIELD_NA, IO_EVENT_WAIT_TIME, MAX_NO_BOARDS_PER_PAGE, MAX_NO_CARDS_PER_BOARD, MAX_TICKRATE,
15        MAX_WARNING_DUE_DATE_DAYS, MIN_NO_BOARDS_PER_PAGE, MIN_NO_CARDS_PER_BOARD, MIN_TICKRATE,
16        MIN_WARNING_DUE_DATE_DAYS,
17    },
18    inputs::{key::Key, mouse::Mouse},
19    io::{
20        data_handler::{self, get_available_local_save_files, get_default_save_directory},
21        io_handler::refresh_visible_boards_and_cards,
22        logger::{get_logs, RUST_KANBAN_LOGGER},
23        IoEvent,
24    },
25    ui::{
26        text_box::TextBox,
27        theme::Theme,
28        widgets::{
29            date_time_picker::CalenderType,
30            toast::{Toast, ToastType},
31            Widgets,
32        },
33        PopUp, TextColorOptions, TextModifierOptions, View,
34    },
35};
36use linked_hash_map::LinkedHashMap;
37use log::{debug, error, warn};
38use ratatui::widgets::TableState;
39use serde::{Deserialize, Serialize};
40use serde_json::Value;
41use state::AppState;
42use std::{
43    collections::HashMap,
44    fmt::{self, Display, Formatter},
45    path::PathBuf,
46    str::FromStr,
47    time::{Duration, Instant},
48    vec,
49};
50use strum::{EnumString, IntoEnumIterator};
51use strum_macros::EnumIter;
52
53pub mod actions;
54pub mod app_helper;
55pub mod kanban;
56pub mod state;
57
58#[derive(Debug, PartialEq, Eq)]
59pub enum AppReturn {
60    Exit,
61    Continue,
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
65pub enum ActionHistory {
66    /// card, board_id
67    DeleteCard(Card, (u64, u64)),
68    /// card, board_id
69    CreateCard(Card, (u64, u64)),
70    /// board
71    DeleteBoard(Board),
72    /// card, moved_from_board_id, moved_to_board_id, moved_from_index, moved_to_index
73    MoveCardBetweenBoards(Card, (u64, u64), (u64, u64), usize, usize),
74    /// board_id, moved_from_index, moved_to_index
75    MoveCardWithinBoard((u64, u64), usize, usize),
76    /// board
77    CreateBoard(Board),
78    /// old_card, new_card, board_id
79    EditCard(Card, Card, (u64, u64)),
80}
81
82#[derive(Default)]
83pub struct ActionHistoryManager {
84    pub history: Vec<ActionHistory>,
85    pub history_index: usize,
86}
87
88impl ActionHistoryManager {
89    pub fn new_action(&mut self, action: ActionHistory) {
90        if self.history_index != self.history.len() {
91            self.history.truncate(self.history_index);
92        }
93        self.history.push(action);
94        self.history_index += 1;
95    }
96    pub fn reset(&mut self) {
97        self.history.clear();
98        self.history_index = 0;
99    }
100}
101
102pub struct App<'a> {
103    io_tx: tokio::sync::mpsc::Sender<IoEvent>,
104    actions: Vec<Action>,
105    is_loading: bool,
106    pub debug_mode: bool,
107    pub state: AppState<'a>,
108    pub boards: Boards,
109    pub filtered_boards: Boards,
110    pub preview_boards_and_cards: Option<Boards>,
111    pub config: AppConfig,
112    pub visible_boards_and_cards: LinkedHashMap<(u64, u64), Vec<(u64, u64)>>,
113    pub last_io_event_time: Option<Instant>,
114    pub all_themes: Vec<Theme>,
115    pub current_theme: Theme,
116    pub action_history_manager: ActionHistoryManager,
117    pub main_menu: MainMenu,
118    pub widgets: Widgets<'a>,
119}
120
121impl App<'_> {
122    pub fn new(io_tx: tokio::sync::mpsc::Sender<IoEvent>, debug_mode: bool) -> Self {
123        let actions = vec![Action::Quit];
124        let is_loading = false;
125        let state = AppState::default();
126        let boards = Boards::default();
127        let filtered_boards = Boards::default();
128        let all_themes = Theme::all_default_themes();
129        let mut theme = Theme::default();
130        let (config, config_errors, toasts) = prepare_config_for_new_app(theme.clone());
131        let default_theme = config.default_theme.clone();
132        let theme_in_all = all_themes.iter().find(|t| t.name == default_theme);
133        if let Some(theme_in_all) = theme_in_all {
134            theme = theme_in_all.clone();
135        }
136        let mut widgets = Widgets::new(
137            theme.clone(),
138            debug_mode,
139            config.date_picker_calender_format.clone(),
140        );
141        widgets.toast_widget.toasts = toasts;
142        let mut app = Self {
143            io_tx,
144            actions,
145            is_loading,
146            debug_mode,
147            state,
148            boards,
149            filtered_boards,
150            preview_boards_and_cards: None,
151            config,
152            visible_boards_and_cards: LinkedHashMap::new(),
153            last_io_event_time: None,
154            all_themes,
155            current_theme: theme,
156            action_history_manager: ActionHistoryManager::default(),
157            main_menu: MainMenu::default(),
158            widgets,
159        };
160        if !config_errors.is_empty() {
161            for error in config_errors {
162                app.send_error_toast(error, None);
163            }
164        }
165        app
166    }
167
168    pub async fn do_action(&mut self, key: Key) -> AppReturn {
169        if self.state.app_status == AppStatus::UserInput {
170            handle_user_input_mode(self, key).await
171        } else if self.state.app_status == AppStatus::KeyBindMode {
172            handle_edit_keybinding_mode(self, key).await
173        } else {
174            handle_general_actions(self, key).await
175        }
176    }
177    pub async fn dispatch(&mut self, action: IoEvent) {
178        self.is_loading = true;
179        if self
180            .last_io_event_time
181            .unwrap_or_else(|| Instant::now() - Duration::from_millis(IO_EVENT_WAIT_TIME + 10))
182            + Duration::from_millis(IO_EVENT_WAIT_TIME)
183            > Instant::now()
184        {
185            tokio::time::sleep(Duration::from_millis(IO_EVENT_WAIT_TIME)).await;
186        }
187        self.last_io_event_time = Some(Instant::now());
188        if let Err(e) = self.io_tx.send(action).await {
189            self.is_loading = false;
190            debug!("Error from dispatch {}", e);
191            error!("Error in handling request please, restart the app");
192            self.send_error_toast("Error in handling request please, restart the app", None);
193        };
194    }
195
196    pub async fn handle_mouse(&mut self, mouse_action: Mouse) -> AppReturn {
197        if self.config.enable_mouse_support {
198            handle_mouse_action(self, mouse_action).await
199        } else {
200            AppReturn::Continue
201        }
202    }
203    pub fn get_first_keybinding(&self, keybinding_enum: KeyBindingEnum) -> Option<String> {
204        self.config
205            .keybindings
206            .get_keybindings(keybinding_enum)
207            .and_then(|keys| keys.first().cloned())
208            .map(|key| key.to_string())
209    }
210    pub fn status(&self) -> &AppStatus {
211        &self.state.app_status
212    }
213    pub fn is_loading(&self) -> bool {
214        self.is_loading
215    }
216    pub fn initialized(&mut self) {
217        self.actions = Action::all();
218        self.state.set_focus(Focus::Body);
219        self.state.app_status = AppStatus::initialized()
220    }
221    pub fn loaded(&mut self) {
222        self.is_loading = false;
223    }
224    pub fn get_current_focus(&self) -> &Focus {
225        &self.state.focus
226    }
227    pub fn set_config_state(&mut self, config_state: TableState) {
228        self.state.app_table_states.config = config_state;
229    }
230    pub fn config_next(&mut self) {
231        let i = match self.state.app_table_states.config.selected() {
232            Some(i) => {
233                if i >= self.config.to_view_list().len() - 1 {
234                    0
235                } else {
236                    i + 1
237                }
238            }
239            None => 0,
240        };
241        self.state.app_table_states.config.select(Some(i));
242    }
243    pub fn config_prv(&mut self) {
244        let i = match self.state.app_table_states.config.selected() {
245            Some(i) => {
246                if i == 0 {
247                    self.config.to_view_list().len() - 1
248                } else {
249                    i - 1
250                }
251            }
252            None => 0,
253        };
254        self.state.app_table_states.config.select(Some(i));
255    }
256    pub fn main_menu_next(&mut self) {
257        let i = match self.state.app_list_states.main_menu.selected() {
258            Some(i) => {
259                if i >= self.main_menu.all().len() - 1 {
260                    0
261                } else {
262                    i + 1
263                }
264            }
265            None => 0,
266        };
267        self.state.app_list_states.main_menu.select(Some(i));
268    }
269    pub fn main_menu_prv(&mut self) {
270        let i = match self.state.app_list_states.main_menu.selected() {
271            Some(i) => {
272                if i == 0 {
273                    self.main_menu.items.len() - 1
274                } else {
275                    i - 1
276                }
277            }
278            None => 0,
279        };
280        self.state.app_list_states.main_menu.select(Some(i));
281    }
282    pub fn load_save_next(&mut self, cloud_mode: bool) {
283        let i = match self.state.app_list_states.load_save.selected() {
284            Some(i) => {
285                if cloud_mode {
286                    let cloud_save_files = self.state.cloud_data.clone();
287                    let cloud_save_files_len = if let Some(cloud_save_files_len) = cloud_save_files
288                    {
289                        cloud_save_files_len.len()
290                    } else {
291                        0
292                    };
293                    if cloud_save_files_len == 0 || i >= cloud_save_files_len - 1 {
294                        0
295                    } else {
296                        i + 1
297                    }
298                } else {
299                    let local_save_files = get_available_local_save_files(&self.config);
300                    let local_save_files_len = if let Some(local_save_files_len) = local_save_files
301                    {
302                        local_save_files_len.len()
303                    } else {
304                        0
305                    };
306                    if local_save_files_len == 0 || i >= local_save_files_len - 1 {
307                        0
308                    } else {
309                        i + 1
310                    }
311                }
312            }
313            None => 0,
314        };
315        self.state.app_list_states.load_save.select(Some(i));
316    }
317    pub fn load_save_prv(&mut self, cloud_mode: bool) {
318        let i = match self.state.app_list_states.load_save.selected() {
319            Some(i) => {
320                if cloud_mode {
321                    let cloud_save_files = self.state.cloud_data.clone();
322                    let cloud_save_files_len = if let Some(cloud_save_files_len) = cloud_save_files
323                    {
324                        cloud_save_files_len.len()
325                    } else {
326                        0
327                    };
328                    if i == 0 && cloud_save_files_len != 0 {
329                        cloud_save_files_len - 1
330                    } else if cloud_save_files_len == 0 {
331                        0
332                    } else {
333                        i - 1
334                    }
335                } else {
336                    let local_save_files = get_available_local_save_files(&self.config);
337                    let local_save_files_len = if let Some(local_save_files_len) = local_save_files
338                    {
339                        local_save_files_len.len()
340                    } else {
341                        0
342                    };
343                    if i == 0 && local_save_files_len != 0 {
344                        local_save_files_len - 1
345                    } else if local_save_files_len == 0 {
346                        0
347                    } else {
348                        i - 1
349                    }
350                }
351            }
352            None => 0,
353        };
354        self.state.app_list_states.load_save.select(Some(i));
355    }
356    pub fn config_state(&self) -> &TableState {
357        &self.state.app_table_states.config
358    }
359    pub fn edit_keybindings_next(&mut self) {
360        let keybinding_iterator = self.config.keybindings.iter();
361        let i = match self.state.app_table_states.edit_keybindings.selected() {
362            Some(i) => {
363                if i >= keybinding_iterator.count() - 1 {
364                    0
365                } else {
366                    i + 1
367                }
368            }
369            None => 0,
370        };
371        self.state.app_table_states.edit_keybindings.select(Some(i));
372    }
373    pub fn edit_keybindings_prv(&mut self) {
374        let keybinding_iterator = self.config.keybindings.iter();
375        let i = match self.state.app_table_states.edit_keybindings.selected() {
376            Some(i) => {
377                if i == 0 {
378                    keybinding_iterator.count() - 1
379                } else {
380                    i - 1
381                }
382            }
383            None => 0,
384        };
385        self.state.app_table_states.edit_keybindings.select(Some(i));
386    }
387    pub fn help_next(&mut self) {
388        let all_keybindings: Vec<_> = self.config.keybindings.iter().collect();
389        let i = match self.state.app_table_states.help.selected() {
390            Some(i) => {
391                if !all_keybindings.is_empty() {
392                    if i >= (all_keybindings.len() / 2) - 1 {
393                        0
394                    } else {
395                        i + 1
396                    }
397                } else {
398                    0
399                }
400            }
401            None => 0,
402        };
403        self.state.app_table_states.help.select(Some(i));
404    }
405    pub fn help_prv(&mut self) {
406        let all_keybindings: Vec<_> = self.config.keybindings.iter().collect();
407        let i = match self.state.app_table_states.help.selected() {
408            Some(i) => {
409                if !all_keybindings.is_empty() {
410                    if i == 0 {
411                        (all_keybindings.len() / 2) - 1
412                    } else {
413                        i - 1
414                    }
415                } else {
416                    0
417                }
418            }
419            None => 0,
420        };
421        self.state.app_table_states.help.select(Some(i));
422    }
423    pub fn select_default_view_next(&mut self) {
424        let i = match self.state.app_list_states.default_view.selected() {
425            Some(i) => {
426                if i >= View::all_views_as_string().len() - 1 {
427                    0
428                } else {
429                    i + 1
430                }
431            }
432            None => 0,
433        };
434        self.state.app_list_states.default_view.select(Some(i));
435    }
436    pub fn select_default_view_prv(&mut self) {
437        let i = match self.state.app_list_states.default_view.selected() {
438            Some(i) => {
439                if i == 0 {
440                    View::all_views_as_string().len() - 1
441                } else {
442                    i - 1
443                }
444            }
445            None => 0,
446        };
447        self.state.app_list_states.default_view.select(Some(i));
448    }
449    pub fn command_palette_command_search_prv(&mut self) {
450        let i = match self
451            .state
452            .app_list_states
453            .command_palette_command_search
454            .selected()
455        {
456            Some(i) => {
457                if let Some(results) = &self.widgets.command_palette.command_search_results {
458                    if i == 0 {
459                        results.len() - 1
460                    } else {
461                        i - 1
462                    }
463                } else {
464                    0
465                }
466            }
467            None => 0,
468        };
469        self.state
470            .app_list_states
471            .command_palette_command_search
472            .select(Some(i));
473    }
474    pub fn command_palette_command_search_next(&mut self) {
475        let i = match self
476            .state
477            .app_list_states
478            .command_palette_command_search
479            .selected()
480        {
481            Some(i) => {
482                if let Some(results) = &self.widgets.command_palette.command_search_results {
483                    if i >= results.len() - 1 {
484                        0
485                    } else {
486                        i + 1
487                    }
488                } else {
489                    0
490                }
491            }
492            None => 0,
493        };
494        self.state
495            .app_list_states
496            .command_palette_command_search
497            .select(Some(i));
498    }
499    pub fn command_palette_card_search_next(&mut self) {
500        let i = match self
501            .state
502            .app_list_states
503            .command_palette_card_search
504            .selected()
505        {
506            Some(i) => {
507                if let Some(results) = &self.widgets.command_palette.card_search_results {
508                    if i >= results.len() - 1 {
509                        0
510                    } else {
511                        i + 1
512                    }
513                } else {
514                    0
515                }
516            }
517            None => 0,
518        };
519        self.state
520            .app_list_states
521            .command_palette_card_search
522            .select(Some(i));
523    }
524    pub fn command_palette_card_search_prv(&mut self) {
525        let i = match self
526            .state
527            .app_list_states
528            .command_palette_card_search
529            .selected()
530        {
531            Some(i) => {
532                if let Some(results) = &self.widgets.command_palette.card_search_results {
533                    if i == 0 {
534                        results.len() - 1
535                    } else {
536                        i - 1
537                    }
538                } else {
539                    0
540                }
541            }
542            None => 0,
543        };
544        self.state
545            .app_list_states
546            .command_palette_card_search
547            .select(Some(i));
548    }
549    pub fn command_palette_board_search_next(&mut self) {
550        let i = match self
551            .state
552            .app_list_states
553            .command_palette_board_search
554            .selected()
555        {
556            Some(i) => {
557                if let Some(results) = &self.widgets.command_palette.board_search_results {
558                    if i >= results.len() - 1 {
559                        0
560                    } else {
561                        i + 1
562                    }
563                } else {
564                    0
565                }
566            }
567            None => 0,
568        };
569        self.state
570            .app_list_states
571            .command_palette_board_search
572            .select(Some(i));
573    }
574    pub fn command_palette_board_search_prv(&mut self) {
575        let i = match self
576            .state
577            .app_list_states
578            .command_palette_board_search
579            .selected()
580        {
581            Some(i) => {
582                if let Some(results) = &self.widgets.command_palette.board_search_results {
583                    if i == 0 {
584                        results.len() - 1
585                    } else {
586                        i - 1
587                    }
588                } else {
589                    0
590                }
591            }
592            None => 0,
593        };
594        self.state
595            .app_list_states
596            .command_palette_board_search
597            .select(Some(i));
598    }
599    pub fn send_info_toast(&mut self, message: &str, custom_duration: Option<Duration>) {
600        if let Some(duration) = custom_duration {
601            self.widgets.toast_widget.toasts.push(Toast::new(
602                message.to_string(),
603                duration,
604                ToastType::Info,
605                self.current_theme.clone(),
606            ));
607        } else {
608            self.widgets.toast_widget.toasts.push(Toast::new(
609                message.to_string(),
610                Duration::from_secs(DEFAULT_TOAST_DURATION),
611                ToastType::Info,
612                self.current_theme.clone(),
613            ));
614        }
615    }
616    pub fn send_error_toast(&mut self, message: &str, custom_duration: Option<Duration>) {
617        if let Some(duration) = custom_duration {
618            self.widgets.toast_widget.toasts.push(Toast::new(
619                message.to_string(),
620                duration,
621                ToastType::Error,
622                self.current_theme.clone(),
623            ));
624        } else {
625            self.widgets.toast_widget.toasts.push(Toast::new(
626                message.to_string(),
627                Duration::from_secs(DEFAULT_TOAST_DURATION),
628                ToastType::Error,
629                self.current_theme.clone(),
630            ));
631        }
632    }
633    pub fn send_warning_toast(&mut self, message: &str, custom_duration: Option<Duration>) {
634        if let Some(duration) = custom_duration {
635            self.widgets.toast_widget.toasts.push(Toast::new(
636                message.to_string(),
637                duration,
638                ToastType::Warning,
639                self.current_theme.clone(),
640            ));
641        } else {
642            self.widgets.toast_widget.toasts.push(Toast::new(
643                message.to_string(),
644                Duration::from_secs(DEFAULT_TOAST_DURATION),
645                ToastType::Warning,
646                self.current_theme.clone(),
647            ));
648        }
649    }
650    pub fn select_card_status_prv(&mut self) {
651        let i = match self.state.app_list_states.card_status_selector.selected() {
652            Some(i) => {
653                if i == 0 {
654                    CardStatus::all().len() - 1
655                } else {
656                    i - 1
657                }
658            }
659            None => 0,
660        };
661        self.state
662            .app_list_states
663            .card_status_selector
664            .select(Some(i));
665    }
666    pub fn select_card_status_next(&mut self) {
667        let i = match self.state.app_list_states.card_status_selector.selected() {
668            Some(i) => {
669                if i >= CardStatus::all().len() - 1 {
670                    0
671                } else {
672                    i + 1
673                }
674            }
675            None => 0,
676        };
677        self.state
678            .app_list_states
679            .card_status_selector
680            .select(Some(i));
681    }
682    pub fn select_change_theme_next(&mut self) {
683        let i = match self.state.app_list_states.theme_selector.selected() {
684            Some(i) => {
685                if i >= self.all_themes.len() - 1 {
686                    0
687                } else {
688                    i + 1
689                }
690            }
691            None => 0,
692        };
693        self.state.app_list_states.theme_selector.select(Some(i));
694        self.current_theme = self.all_themes[i].clone();
695    }
696    pub fn select_change_theme_prv(&mut self) {
697        let i = match self.state.app_list_states.theme_selector.selected() {
698            Some(i) => {
699                if i == 0 {
700                    self.all_themes.len() - 1
701                } else {
702                    i - 1
703                }
704            }
705            None => 0,
706        };
707        self.state.app_list_states.theme_selector.select(Some(i));
708        self.current_theme = self.all_themes[i].clone();
709    }
710    pub fn select_create_theme_next(&mut self) {
711        // popup doesn't matter here, as we only want the length of the rows
712        let theme_rows_len = Theme::default().to_rows(self, true).1.len();
713        let i = match self.state.app_table_states.theme_editor.selected() {
714            Some(i) => {
715                if i >= theme_rows_len - 1 {
716                    0
717                } else {
718                    i + 1
719                }
720            }
721            None => 0,
722        };
723        self.state.app_table_states.theme_editor.select(Some(i));
724    }
725    pub fn select_create_theme_prv(&mut self) {
726        // popup doesn't matter here, as we only want the length of the rows
727        let theme_rows_len = Theme::default().to_rows(self, true).1.len();
728        let i = match self.state.app_table_states.theme_editor.selected() {
729            Some(i) => {
730                if i == 0 {
731                    theme_rows_len - 1
732                } else {
733                    i - 1
734                }
735            }
736            None => 0,
737        };
738        self.state.app_table_states.theme_editor.select(Some(i));
739    }
740    pub fn select_edit_style_fg_next(&mut self) {
741        let i = match self.state.app_list_states.edit_specific_style[0].selected() {
742            Some(i) => {
743                if i >= TextColorOptions::iter().count() - 1 {
744                    0
745                } else {
746                    i + 1
747                }
748            }
749            None => 0,
750        };
751        self.state.app_list_states.edit_specific_style[0].select(Some(i));
752    }
753    pub fn select_edit_style_fg_prv(&mut self) {
754        let i = match self.state.app_list_states.edit_specific_style[0].selected() {
755            Some(i) => {
756                if i == 0 {
757                    TextColorOptions::iter().count() - 1
758                } else {
759                    i - 1
760                }
761            }
762            None => 0,
763        };
764        self.state.app_list_states.edit_specific_style[0].select(Some(i));
765    }
766    pub fn select_edit_style_bg_next(&mut self) {
767        let i = match self.state.app_list_states.edit_specific_style[1].selected() {
768            Some(i) => {
769                if i >= TextColorOptions::iter().count() - 1 {
770                    0
771                } else {
772                    i + 1
773                }
774            }
775            None => 0,
776        };
777        self.state.app_list_states.edit_specific_style[1].select(Some(i));
778    }
779    pub fn select_edit_style_bg_prv(&mut self) {
780        let i = match self.state.app_list_states.edit_specific_style[1].selected() {
781            Some(i) => {
782                if i == 0 {
783                    TextColorOptions::iter().count() - 1
784                } else {
785                    i - 1
786                }
787            }
788            None => 0,
789        };
790        self.state.app_list_states.edit_specific_style[1].select(Some(i));
791    }
792    pub fn select_edit_style_modifier_next(&mut self) {
793        let i = match self.state.app_list_states.edit_specific_style[2].selected() {
794            Some(i) => {
795                if i >= TextModifierOptions::iter().count() - 1 {
796                    0
797                } else {
798                    i + 1
799                }
800            }
801            None => 0,
802        };
803        self.state.app_list_states.edit_specific_style[2].select(Some(i));
804    }
805    pub fn select_edit_style_modifier_prv(&mut self) {
806        let i = match self.state.app_list_states.edit_specific_style[2].selected() {
807            Some(i) => {
808                if i == 0 {
809                    TextModifierOptions::iter().count() - 1
810                } else {
811                    i - 1
812                }
813            }
814            None => 0,
815        };
816        self.state.app_list_states.edit_specific_style[2].select(Some(i));
817    }
818    pub fn select_card_priority_next(&mut self) {
819        let i = match self.state.app_list_states.card_priority_selector.selected() {
820            Some(i) => {
821                if i >= CardPriority::all().len() - 1 {
822                    0
823                } else {
824                    i + 1
825                }
826            }
827            None => 0,
828        };
829        self.state
830            .app_list_states
831            .card_priority_selector
832            .select(Some(i));
833    }
834    pub fn select_card_priority_prv(&mut self) {
835        let i = match self.state.app_list_states.card_priority_selector.selected() {
836            Some(i) => {
837                if i == 0 {
838                    CardPriority::all().len() - 1
839                } else {
840                    i - 1
841                }
842            }
843            None => 0,
844        };
845        self.state
846            .app_list_states
847            .card_priority_selector
848            .select(Some(i));
849    }
850    pub fn filter_by_tag_popup_next(&mut self) {
851        let all_tags_len = if let Some(available_tags) = &self.state.all_available_tags {
852            available_tags.len()
853        } else {
854            0
855        };
856        if all_tags_len > 0 {
857            let i = match self.state.app_list_states.filter_by_tag_list.selected() {
858                Some(i) => {
859                    if i >= all_tags_len - 1 {
860                        0
861                    } else {
862                        i + 1
863                    }
864                }
865                None => 0,
866            };
867            self.state
868                .app_list_states
869                .filter_by_tag_list
870                .select(Some(i));
871        }
872    }
873    pub fn filter_by_tag_popup_prv(&mut self) {
874        let all_tags_len = if let Some(available_tags) = &self.state.all_available_tags {
875            available_tags.len()
876        } else {
877            0
878        };
879        if all_tags_len > 0 {
880            let i = match self.state.app_list_states.filter_by_tag_list.selected() {
881                Some(i) => {
882                    if i == 0 {
883                        all_tags_len - 1
884                    } else {
885                        i - 1
886                    }
887                }
888                None => 0,
889            };
890            self.state
891                .app_list_states
892                .filter_by_tag_list
893                .select(Some(i));
894        }
895    }
896    pub fn change_date_format_popup_next(&mut self) {
897        let i = match self.state.app_list_states.date_format_selector.selected() {
898            Some(i) => {
899                if i >= DateTimeFormat::get_all_date_formats().len() - 1 {
900                    0
901                } else {
902                    i + 1
903                }
904            }
905            None => 0,
906        };
907        self.state
908            .app_list_states
909            .date_format_selector
910            .select(Some(i));
911    }
912    pub fn change_date_format_popup_prv(&mut self) {
913        let i = match self.state.app_list_states.date_format_selector.selected() {
914            Some(i) => {
915                if i == 0 {
916                    DateTimeFormat::get_all_date_formats().len() - 1
917                } else {
918                    i - 1
919                }
920            }
921            None => 0,
922        };
923        self.state
924            .app_list_states
925            .date_format_selector
926            .select(Some(i));
927    }
928    pub fn undo(&mut self) {
929        if self.action_history_manager.history_index == 0 {
930            self.send_error_toast("No more actions to undo", None);
931        } else {
932            let history_index = self.action_history_manager.history_index - 1;
933            let history = self.action_history_manager.history[history_index].clone();
934            match history {
935                ActionHistory::DeleteCard(card, board_id) => {
936                    if let Some(board) = self.boards.get_mut_board_with_id(board_id) {
937                        board.cards.add_card(card.clone());
938                        self.action_history_manager.history_index -= 1;
939                        refresh_visible_boards_and_cards(self);
940                        self.send_info_toast(&format!("Undo Delete Card '{}'", card.name), None);
941                    } else {
942                        self.send_error_toast(&format!("Could not undo delete card '{}' as the board with id '{:?}' was not found", card.name, board_id), None);
943                    }
944                }
945                ActionHistory::CreateCard(card, board_id) => {
946                    if let Some(board) = self.boards.get_mut_board_with_id(board_id) {
947                        board.cards.remove_card_with_id(card.id);
948                        refresh_visible_boards_and_cards(self);
949                        self.action_history_manager.history_index -= 1;
950                        self.send_info_toast(&format!("Undo Create Card '{}'", card.name), None);
951                    } else {
952                        self.send_error_toast(&format!("Could not undo create card '{}' as the board with id '{:?}' was not found", card.name, board_id), None);
953                    }
954                }
955                ActionHistory::MoveCardBetweenBoards(
956                    card,
957                    moved_from_board_id,
958                    moved_to_board_id,
959                    moved_from_index,
960                    moved_to_index,
961                ) => {
962                    let moved_to_board = self.boards.get_board_with_id(moved_to_board_id);
963                    let moved_from_board = self.boards.get_board_with_id(moved_from_board_id);
964                    if moved_to_board.is_none() || moved_from_board.is_none() {
965                        debug!("Could not undo move card '{}' as the move to board with id '{:?}' or the move from board with id '{:?}' was not found", card.name, moved_to_board_id, moved_from_board_id);
966                        return;
967                    }
968
969                    let moved_from_board = moved_from_board.unwrap();
970                    if moved_from_index > moved_from_board.cards.len() {
971                        debug!("bad index for undo move card, from board {:?}, to board {:?}, from index {}, to index {}", moved_from_board_id, moved_to_board_id, moved_from_index, moved_to_index);
972                        self.send_error_toast(
973                            &format!(
974                                "Could not undo move card '{}' as the index's were invalid",
975                                card.name
976                            ),
977                            None,
978                        );
979                    }
980
981                    let moved_to_board = self
982                        .boards
983                        .get_mut_board_with_id(moved_to_board_id)
984                        .unwrap();
985                    moved_to_board.cards.remove_card_with_id(card.id);
986
987                    let moved_from_board = self
988                        .boards
989                        .get_mut_board_with_id(moved_from_board_id)
990                        .unwrap();
991                    moved_from_board
992                        .cards
993                        .add_card_at_index(moved_from_index, card.clone());
994
995                    refresh_visible_boards_and_cards(self);
996                    self.action_history_manager.history_index -= 1;
997                    self.send_info_toast(&format!("Undo Move Card '{}'", card.name), None);
998                }
999                ActionHistory::MoveCardWithinBoard(board_id, moved_from_index, moved_to_index) => {
1000                    if let Some(board) = self.boards.get_mut_board_with_id(board_id) {
1001                        if moved_from_index >= board.cards.len()
1002                            || moved_to_index >= board.cards.len()
1003                        {
1004                            self.send_error_toast(
1005                                &format!(
1006                                    "Could not undo move card '{}' as the index's were invalid",
1007                                    FIELD_NA
1008                                ),
1009                                None,
1010                            );
1011                            return;
1012                        }
1013                        let card_name = board
1014                            .cards
1015                            .get_mut_card_with_index(moved_to_index)
1016                            .unwrap()
1017                            .name
1018                            .clone();
1019                        board.cards.swap(moved_from_index, moved_to_index);
1020                        refresh_visible_boards_and_cards(self);
1021                        self.action_history_manager.history_index -= 1;
1022                        self.send_info_toast(&format!("Undo Move Card '{}'", card_name), None);
1023                    } else {
1024                        self.send_error_toast(&format!("Could not undo move card '{}' as the board with id '{:?}' was not found",FIELD_NA, board_id), None);
1025                    }
1026                }
1027                ActionHistory::DeleteBoard(board) => {
1028                    self.boards.add_board(board.clone());
1029                    refresh_visible_boards_and_cards(self);
1030                    self.action_history_manager.history_index -= 1;
1031                    self.send_info_toast(&format!("Undo Delete Board '{}'", board.name), None);
1032                }
1033                ActionHistory::CreateBoard(board) => {
1034                    self.boards.remove_board_with_id(board.id);
1035                    refresh_visible_boards_and_cards(self);
1036                    self.action_history_manager.history_index -= 1;
1037                    self.send_info_toast(&format!("Undo Create Board '{}'", board.name), None);
1038                }
1039                ActionHistory::EditCard(old_card, _, board_id) => {
1040                    let mut card_name = String::new();
1041                    let mut card_found = false;
1042                    if let Some(board) = self.boards.get_mut_board_with_id(board_id) {
1043                        if let Some(card) = board.cards.get_mut_card_with_id(old_card.id) {
1044                            *card = old_card.clone();
1045                            card_name.clone_from(&card.name);
1046                            card_found = true;
1047                        } else {
1048                            self.send_error_toast(
1049                                &format!(
1050                                    "Could not undo edit card '{}' as the card was not found",
1051                                    old_card.name
1052                                ),
1053                                None,
1054                            );
1055                        }
1056                    } else {
1057                        self.send_error_toast(&format!("Could not undo edit card '{}' as the board with id '{:?}' was not found", old_card.name, board_id), None);
1058                    }
1059                    if card_found {
1060                        self.action_history_manager.history_index -= 1;
1061                    }
1062                    if !card_name.is_empty() {
1063                        self.send_info_toast(&format!("Undo Edit Card '{}'", card_name), None);
1064                        refresh_visible_boards_and_cards(self);
1065                    }
1066                }
1067            }
1068        }
1069    }
1070
1071    pub fn redo(&mut self) {
1072        if self.action_history_manager.history_index == self.action_history_manager.history.len() {
1073            self.send_error_toast("No more actions to redo", None);
1074        } else {
1075            let history_index = self.action_history_manager.history_index;
1076            let history = self.action_history_manager.history[history_index].clone();
1077            match history {
1078                ActionHistory::DeleteCard(card, board_id) => {
1079                    if let Some(board) = self.boards.get_mut_board_with_id(board_id) {
1080                        board.cards.remove_card_with_id(card.id);
1081                        refresh_visible_boards_and_cards(self);
1082                        self.action_history_manager.history_index += 1;
1083                        self.send_info_toast(&format!("Redo Delete Card '{}'", card.name), None);
1084                    } else {
1085                        self.send_error_toast(&format!("Could not redo delete card '{}' as the board with id '{:?}' was not found", card.name, board_id), None);
1086                    }
1087                }
1088                ActionHistory::CreateCard(card, board_id) => {
1089                    if let Some(board) = self.boards.get_mut_board_with_id(board_id) {
1090                        board.cards.add_card(card.clone());
1091                        refresh_visible_boards_and_cards(self);
1092                        self.action_history_manager.history_index += 1;
1093                        self.send_info_toast(&format!("Redo Create Card '{}'", card.name), None);
1094                    } else {
1095                        self.send_error_toast(&format!("Could not redo create card '{}' as the board with id '{:?}' was not found", card.name, board_id), None);
1096                    }
1097                }
1098                ActionHistory::MoveCardBetweenBoards(
1099                    card,
1100                    moved_from_board_id,
1101                    moved_to_board_id,
1102                    moved_from_index,
1103                    moved_to_index,
1104                ) => {
1105                    let moved_to_board = self.boards.get_board_with_id(moved_to_board_id);
1106                    let moved_from_board = self.boards.get_board_with_id(moved_from_board_id);
1107                    if moved_to_board.is_none() || moved_from_board.is_none() {
1108                        debug!("Could not undo move card '{}' as the move to board with id '{:?}' or the move from board with id '{:?}' was not found", card.name, moved_to_board_id, moved_from_board_id);
1109                        return;
1110                    }
1111
1112                    let moved_to_board = moved_to_board.unwrap();
1113                    if moved_to_index > moved_to_board.cards.len() {
1114                        debug!("bad index for redo move card, from board {:?}, to board {:?}, from index {}, to index {}", moved_from_board_id, moved_to_board_id, moved_from_index, moved_to_index);
1115                        self.send_error_toast(
1116                            &format!(
1117                                "Could not redo move card '{}' as the index's were invalid",
1118                                card.name
1119                            ),
1120                            None,
1121                        );
1122                        return;
1123                    }
1124
1125                    let moved_from_board = self
1126                        .boards
1127                        .get_mut_board_with_id(moved_from_board_id)
1128                        .unwrap();
1129                    moved_from_board.cards.remove_card_with_id(card.id);
1130
1131                    let moved_to_board = self
1132                        .boards
1133                        .get_mut_board_with_id(moved_to_board_id)
1134                        .unwrap();
1135                    moved_to_board
1136                        .cards
1137                        .add_card_at_index(moved_to_index, card.clone());
1138
1139                    refresh_visible_boards_and_cards(self);
1140                    self.action_history_manager.history_index += 1;
1141                    self.send_info_toast(&format!("Redo Move Card '{}'", card.name), None);
1142                }
1143                ActionHistory::MoveCardWithinBoard(board_id, moved_from_index, moved_to_index) => {
1144                    if let Some(board) = self.boards.get_mut_board_with_id(board_id) {
1145                        if moved_from_index >= board.cards.len()
1146                            || moved_to_index >= board.cards.len()
1147                        {
1148                            self.send_error_toast(
1149                                &format!(
1150                                    "Could not redo move card '{}' as the index's were invalid",
1151                                    FIELD_NA
1152                                ),
1153                                None,
1154                            );
1155                            return;
1156                        }
1157                        let card_name = board
1158                            .cards
1159                            .get_card_with_index(moved_to_index)
1160                            .unwrap()
1161                            .name
1162                            .clone();
1163                        board.cards.swap(moved_from_index, moved_to_index);
1164                        refresh_visible_boards_and_cards(self);
1165                        self.action_history_manager.history_index += 1;
1166                        self.send_info_toast(&format!("Redo Move Card '{}'", card_name), None);
1167                    } else {
1168                        self.send_error_toast(&format!("Could not redo move card '{}' as the board with id '{:?}' was not found", FIELD_NA, board_id), None);
1169                    }
1170                }
1171                ActionHistory::DeleteBoard(board) => {
1172                    self.boards.remove_board_with_id(board.id);
1173                    refresh_visible_boards_and_cards(self);
1174                    self.action_history_manager.history_index += 1;
1175                    self.send_info_toast(&format!("Redo Delete Board '{}'", board.name), None);
1176                }
1177                ActionHistory::CreateBoard(board) => {
1178                    self.boards.add_board(board.clone());
1179                    refresh_visible_boards_and_cards(self);
1180                    self.action_history_manager.history_index += 1;
1181                    self.send_info_toast(&format!("Redo Create Board '{}'", board.name), None);
1182                }
1183                ActionHistory::EditCard(_, new_card, board_id) => {
1184                    let mut card_name = String::new();
1185                    let mut card_found = false;
1186                    if let Some(board) = self.boards.get_mut_board_with_id(board_id) {
1187                        if let Some(card) = board.cards.get_mut_card_with_id(new_card.id) {
1188                            *card = new_card.clone();
1189                            card_name.clone_from(&card.name);
1190                            card_found = true;
1191                        } else {
1192                            self.send_error_toast(
1193                                &format!(
1194                                    "Could not redo edit card '{}' as the card was not found",
1195                                    new_card.name
1196                                ),
1197                                None,
1198                            );
1199                        }
1200                    } else {
1201                        self.send_error_toast(&format!("Could not redo edit card '{}' as the board with id '{:?}' was not found", new_card.name, board_id), None);
1202                    }
1203                    if card_found {
1204                        self.action_history_manager.history_index += 1;
1205                    }
1206                    if !card_name.is_empty() {
1207                        self.send_info_toast(&format!("Redo Edit Card '{}'", card_name), None);
1208                        refresh_visible_boards_and_cards(self);
1209                    }
1210                }
1211            }
1212        }
1213    }
1214    pub fn log_next(&mut self) {
1215        let total_logs = get_logs().len();
1216        let mut hot_log = RUST_KANBAN_LOGGER.hot_log.lock();
1217        let i = match hot_log.state.selected() {
1218            Some(i) => {
1219                if i >= total_logs - 1 {
1220                    0
1221                } else {
1222                    i + 1
1223                }
1224            }
1225            None => 0,
1226        };
1227        hot_log.state.select(Some(i));
1228    }
1229    pub fn log_prv(&mut self) {
1230        let total_logs = get_logs().len();
1231        let mut hot_log = RUST_KANBAN_LOGGER.hot_log.lock();
1232        let i = match hot_log.state.selected() {
1233            Some(i) => {
1234                if i == 0 {
1235                    total_logs - 1
1236                } else {
1237                    i - 1
1238                }
1239            }
1240            None => 0,
1241        };
1242        hot_log.state.select(Some(i));
1243    }
1244    pub fn tag_picker_next(&mut self) {
1245        let all_tags_len = self.widgets.tag_picker.available_tags.len();
1246        if all_tags_len > 0 {
1247            let i = match self.state.app_list_states.tag_picker.selected() {
1248                Some(i) => {
1249                    if i >= all_tags_len - 1 {
1250                        0
1251                    } else {
1252                        i + 1
1253                    }
1254                }
1255                None => 0,
1256            };
1257            self.state.app_list_states.tag_picker.select(Some(i));
1258        }
1259    }
1260    pub fn tag_picker_prv(&mut self) {
1261        let all_tags_len = self.widgets.tag_picker.available_tags.len();
1262        if all_tags_len > 0 {
1263            let i = match self.state.app_list_states.tag_picker.selected() {
1264                Some(i) => {
1265                    if i == 0 {
1266                        all_tags_len - 1
1267                    } else {
1268                        i - 1
1269                    }
1270                }
1271                None => 0,
1272            };
1273            self.state.app_list_states.tag_picker.select(Some(i));
1274        }
1275    }
1276    pub fn set_popup(&mut self, popup: PopUp) {
1277        if self.state.z_stack.contains(&popup) {
1278            debug!(
1279                "Popup already set: {:?}, z_stack {:?}",
1280                popup, self.state.z_stack
1281            );
1282            return;
1283        }
1284        self.state.z_stack.push(popup);
1285        let available_focus_targets = popup.get_available_targets();
1286        if !available_focus_targets.contains(&self.state.focus) {
1287            if available_focus_targets.is_empty() {
1288                self.state.set_focus(Focus::NoFocus);
1289            } else if available_focus_targets.len() > 1
1290                && available_focus_targets[0] == Focus::Title
1291            {
1292                self.state.set_focus(available_focus_targets[1]);
1293            } else {
1294                self.state.set_focus(available_focus_targets[0]);
1295            }
1296        }
1297        match popup {
1298            PopUp::ViewCard => {
1299                if self.state.current_board_id.is_none() || self.state.current_card_id.is_none() {
1300                    self.send_error_toast("No card selected", Some(Duration::from_secs(1)));
1301                    return;
1302                }
1303                if let Some(current_board) = self
1304                    .boards
1305                    .get_board_with_id(self.state.current_board_id.unwrap())
1306                {
1307                    if let Some(current_card) = current_board
1308                        .cards
1309                        .get_card_with_id(self.state.current_card_id.unwrap())
1310                    {
1311                        self.state.set_focus(Focus::CardName);
1312                        self.state.text_buffers.card_name =
1313                            TextBox::from_string_with_newline_sep(current_card.name.clone(), true);
1314                        self.state.text_buffers.card_description =
1315                            TextBox::from_string_with_newline_sep(
1316                                current_card.description.clone(),
1317                                false,
1318                            );
1319                    } else {
1320                        self.send_error_toast("No card selected", Some(Duration::from_secs(1)));
1321                    }
1322                } else {
1323                    self.send_error_toast("No board selected", Some(Duration::from_secs(1)));
1324                }
1325            }
1326            PopUp::CommandPalette => {
1327                self.widgets.command_palette.reset(&mut self.state);
1328                self.state.app_status = AppStatus::UserInput;
1329                self.state.set_focus(Focus::CommandPaletteCommand);
1330            }
1331            PopUp::CardStatusSelector => {
1332                self.state.set_focus(Focus::ChangeCardStatusPopup);
1333            }
1334            PopUp::CardPrioritySelector => {
1335                self.state.set_focus(Focus::ChangeCardPriorityPopup);
1336            }
1337            PopUp::EditGeneralConfig => {
1338                self.state.set_focus(Focus::EditGeneralConfigPopup);
1339            }
1340            PopUp::CustomHexColorPromptBG | PopUp::CustomHexColorPromptFG => {
1341                self.state.set_focus(Focus::TextInput);
1342                self.state.app_status = AppStatus::UserInput;
1343            }
1344            PopUp::DateTimePicker => {
1345                self.widgets.date_time_picker.open_date_picker();
1346            }
1347            _ => {
1348                debug!("No special logic for setting popup: {:?}", popup);
1349            }
1350        }
1351    }
1352
1353    pub fn close_popup(&mut self) {
1354        if let Some(popup) = self.state.z_stack.pop() {
1355            match popup {
1356                PopUp::CustomHexColorPromptBG | PopUp::CustomHexColorPromptFG => {
1357                    self.state.app_status = AppStatus::Initialized;
1358                }
1359                PopUp::ViewCard => {
1360                    self.state.app_status = AppStatus::Initialized;
1361                    if self.state.card_being_edited.is_some() {
1362                        self.set_popup(PopUp::ConfirmDiscardCardChanges);
1363                    }
1364                }
1365                PopUp::ConfirmDiscardCardChanges => {
1366                    self.state.app_status = AppStatus::Initialized;
1367                    if let Some(card) = &self.state.card_being_edited {
1368                        warn!("Discarding changes to card '{}'", card.1.name);
1369                        self.send_warning_toast(
1370                            &format!("Discarding changes to card '{}'", card.1.name),
1371                            None,
1372                        );
1373                        self.state.card_being_edited = None;
1374                    }
1375                }
1376                PopUp::DateTimePicker => {
1377                    self.widgets.date_time_picker.close_date_picker();
1378                }
1379                _ => {}
1380            }
1381        }
1382    }
1383
1384    pub fn set_view(&mut self, view: View) {
1385        if let Some(prv_view) = self.state.prev_view {
1386            if prv_view == view {
1387                self.state.prev_view = None;
1388            } else {
1389                self.state.prev_view = Some(self.state.current_view);
1390            }
1391        } else {
1392            self.state.prev_view = Some(self.state.current_view);
1393        }
1394        self.state.current_view = view;
1395        let available_focus_targets = self.state.current_view.get_available_targets();
1396        if !available_focus_targets.contains(&self.state.focus) {
1397            if available_focus_targets.is_empty() {
1398                self.state.set_focus(Focus::NoFocus);
1399            } else if available_focus_targets.len() > 1
1400                && available_focus_targets[0] == Focus::Title
1401            {
1402                self.state.set_focus(available_focus_targets[1]);
1403            } else {
1404                self.state.set_focus(available_focus_targets[0]);
1405            }
1406        }
1407        match view {
1408            View::Login => {
1409                self.state.text_buffers.email_id.reset();
1410                self.state.text_buffers.password.reset();
1411            }
1412            View::SignUp => {
1413                self.state.text_buffers.email_id.reset();
1414                self.state.text_buffers.password.reset();
1415                self.state.text_buffers.confirm_password.reset();
1416            }
1417            View::ResetPassword => {
1418                self.state.text_buffers.email_id.reset();
1419                self.state.text_buffers.password.reset();
1420                self.state.text_buffers.confirm_password.reset();
1421                self.state.text_buffers.reset_password_link.reset();
1422            }
1423            View::CreateTheme => {
1424                self.state.text_buffers.general_config.reset();
1425                self.state.app_table_states.theme_editor.select(Some(0));
1426            }
1427            View::ConfigMenu => self.state.app_table_states.config.select(Some(0)),
1428            _ => {
1429                debug!("No special logic for setting view: {:?}", view);
1430            }
1431        }
1432    }
1433
1434    pub fn get_first_next_focus_keybinding(&self) -> &Key {
1435        self.config
1436            .keybindings
1437            .next_focus
1438            .first()
1439            .unwrap_or(&Key::Tab)
1440    }
1441
1442    pub fn get_first_prv_focus_keybinding(&self) -> &Key {
1443        self.config
1444            .keybindings
1445            .prv_focus
1446            .first()
1447            .unwrap_or(&Key::BackTab)
1448    }
1449
1450    pub fn calculate_tags(&self) -> Vec<(String, u32)> {
1451        let mut tags: Vec<(String, String)> = vec![];
1452        for board in self.boards.get_boards() {
1453            for card in board.cards.get_all_cards() {
1454                for tag in &card.tags {
1455                    if tag.is_empty() {
1456                        continue;
1457                    }
1458                    tags.push((tag.clone(), tag.to_lowercase()));
1459                }
1460            }
1461        }
1462
1463        let count_hash: HashMap<String, (String, u32)> =
1464            tags.iter()
1465                .fold(HashMap::new(), |mut acc, (original, lower)| {
1466                    let entry = acc.entry(lower.clone()).or_insert((original.clone(), 0));
1467                    entry.1 += 1;
1468                    acc
1469                });
1470
1471        let mut tags: Vec<(String, u32)> = count_hash
1472            .iter()
1473            .map(|(_, (original, count))| (original.clone(), *count))
1474            .collect();
1475
1476        tags.sort_by(|a, b| {
1477            if a.1 == b.1 {
1478                a.0.to_lowercase().cmp(&b.0.to_lowercase())
1479            } else {
1480                b.1.cmp(&a.1)
1481            }
1482        });
1483
1484        tags
1485    }
1486}
1487
1488// TODO: Refactor to keep all structs and enums separate from other code (maybe? think about this)
1489#[derive(Serialize, Deserialize, Debug, Clone)]
1490pub enum MainMenuItem {
1491    View,
1492    Config,
1493    Help,
1494    LoadSaveLocal,
1495    LoadSaveCloud,
1496    Quit,
1497}
1498
1499impl Display for MainMenuItem {
1500    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
1501        match *self {
1502            MainMenuItem::View => write!(f, "View your Boards"),
1503            MainMenuItem::Config => write!(f, "Configure"),
1504            MainMenuItem::Help => write!(f, "Help"),
1505            MainMenuItem::LoadSaveLocal => write!(f, "Load a Save (local)"),
1506            MainMenuItem::LoadSaveCloud => write!(f, "Load a Save (cloud)"),
1507            MainMenuItem::Quit => write!(f, "Quit"),
1508        }
1509    }
1510}
1511
1512#[derive(Debug, Clone)]
1513pub struct MainMenu {
1514    pub items: Vec<MainMenuItem>,
1515    pub logged_in: bool,
1516}
1517
1518impl Default for MainMenu {
1519    fn default() -> Self {
1520        MainMenu {
1521            items: vec![
1522                MainMenuItem::View,
1523                MainMenuItem::Config,
1524                MainMenuItem::Help,
1525                MainMenuItem::LoadSaveLocal,
1526                MainMenuItem::Quit,
1527            ],
1528            logged_in: false,
1529        }
1530    }
1531}
1532
1533impl MainMenu {
1534    pub fn all(&mut self) -> Vec<MainMenuItem> {
1535        if self.logged_in {
1536            let return_vec = vec![
1537                MainMenuItem::View,
1538                MainMenuItem::Config,
1539                MainMenuItem::Help,
1540                MainMenuItem::LoadSaveLocal,
1541                MainMenuItem::LoadSaveCloud,
1542                MainMenuItem::Quit,
1543            ];
1544            self.items.clone_from(&return_vec);
1545            return_vec
1546        } else {
1547            let return_vec = vec![
1548                MainMenuItem::View,
1549                MainMenuItem::Config,
1550                MainMenuItem::Help,
1551                MainMenuItem::LoadSaveLocal,
1552                MainMenuItem::Quit,
1553            ];
1554            self.items.clone_from(&return_vec);
1555            return_vec
1556        }
1557    }
1558
1559    pub fn from_index(&self, index: usize) -> MainMenuItem {
1560        if self.logged_in {
1561            match index {
1562                0 => MainMenuItem::View,
1563                1 => MainMenuItem::Config,
1564                2 => MainMenuItem::Help,
1565                3 => MainMenuItem::LoadSaveLocal,
1566                4 => MainMenuItem::LoadSaveCloud,
1567                5 => MainMenuItem::Quit,
1568                _ => MainMenuItem::Quit,
1569            }
1570        } else {
1571            match index {
1572                0 => MainMenuItem::View,
1573                1 => MainMenuItem::Config,
1574                2 => MainMenuItem::Help,
1575                3 => MainMenuItem::LoadSaveLocal,
1576                4 => MainMenuItem::Quit,
1577                _ => MainMenuItem::Quit,
1578            }
1579        }
1580    }
1581}
1582
1583#[derive(Serialize, Deserialize, Debug, Clone, Copy, Default, PartialEq, EnumString)]
1584pub enum DateTimeFormat {
1585    DayMonthYear,
1586    #[default]
1587    DayMonthYearTime,
1588    MonthDayYear,
1589    MonthDayYearTime,
1590    YearMonthDay,
1591    YearMonthDayTime,
1592}
1593
1594impl DateTimeFormat {
1595    pub fn to_human_readable_string(&self) -> &str {
1596        match self {
1597            DateTimeFormat::DayMonthYear => "DD/MM/YYYY",
1598            DateTimeFormat::DayMonthYearTime => "DD/MM/YYYY-HH:MM:SS",
1599            DateTimeFormat::MonthDayYear => "MM/DD/YYYY",
1600            DateTimeFormat::MonthDayYearTime => "MM/DD/YYYY-HH:MM:SS",
1601            DateTimeFormat::YearMonthDay => "YYYY/MM/DD",
1602            DateTimeFormat::YearMonthDayTime => "YYYY/MM/DD-HH:MM:SS",
1603        }
1604    }
1605    pub fn to_parser_string(&self) -> &str {
1606        match self {
1607            DateTimeFormat::DayMonthYear => "%d/%m/%Y",
1608            DateTimeFormat::DayMonthYearTime => "%d/%m/%Y-%H:%M:%S",
1609            DateTimeFormat::MonthDayYear => "%m/%d/%Y",
1610            DateTimeFormat::MonthDayYearTime => "%m/%d/%Y-%H:%M:%S",
1611            DateTimeFormat::YearMonthDay => "%Y/%m/%d",
1612            DateTimeFormat::YearMonthDayTime => "%Y/%m/%d-%H:%M:%S",
1613        }
1614    }
1615    pub fn from_json_string(json_string: &str) -> Option<DateTimeFormat> {
1616        match DateTimeFormat::from_str(json_string) {
1617            Ok(date_time_format) => Some(date_time_format),
1618            Err(_) => None,
1619        }
1620    }
1621    pub fn from_human_readable_string(human_readable_string: &str) -> Option<DateTimeFormat> {
1622        match human_readable_string {
1623            "DD/MM/YYYY" => Some(DateTimeFormat::DayMonthYear),
1624            "DD/MM/YYYY-HH:MM:SS" => Some(DateTimeFormat::DayMonthYearTime),
1625            "MM/DD/YYYY" => Some(DateTimeFormat::MonthDayYear),
1626            "MM/DD/YYYY-HH:MM:SS" => Some(DateTimeFormat::MonthDayYearTime),
1627            "YYYY/MM/DD" => Some(DateTimeFormat::YearMonthDay),
1628            "YYYY/MM/DD-HH:MM:SS" => Some(DateTimeFormat::YearMonthDayTime),
1629            _ => None,
1630        }
1631    }
1632    pub fn get_all_date_formats() -> Vec<DateTimeFormat> {
1633        vec![
1634            DateTimeFormat::DayMonthYear,
1635            DateTimeFormat::DayMonthYearTime,
1636            DateTimeFormat::MonthDayYear,
1637            DateTimeFormat::MonthDayYearTime,
1638            DateTimeFormat::YearMonthDay,
1639            DateTimeFormat::YearMonthDayTime,
1640        ]
1641    }
1642    pub fn all_formats_with_time() -> Vec<DateTimeFormat> {
1643        vec![
1644            DateTimeFormat::DayMonthYearTime,
1645            DateTimeFormat::MonthDayYearTime,
1646            DateTimeFormat::YearMonthDayTime,
1647        ]
1648    }
1649    pub fn all_formats_without_time() -> Vec<DateTimeFormat> {
1650        vec![
1651            DateTimeFormat::DayMonthYear,
1652            DateTimeFormat::MonthDayYear,
1653            DateTimeFormat::YearMonthDay,
1654        ]
1655    }
1656    pub fn add_time_to_date_format(date_format: DateTimeFormat) -> DateTimeFormat {
1657        match date_format {
1658            DateTimeFormat::DayMonthYear => DateTimeFormat::DayMonthYearTime,
1659            DateTimeFormat::MonthDayYear => DateTimeFormat::MonthDayYearTime,
1660            DateTimeFormat::YearMonthDay => DateTimeFormat::YearMonthDayTime,
1661            _ => date_format,
1662        }
1663    }
1664    pub fn remove_time_from_date_format(date_format: DateTimeFormat) -> DateTimeFormat {
1665        match date_format {
1666            DateTimeFormat::DayMonthYearTime => DateTimeFormat::DayMonthYear,
1667            DateTimeFormat::MonthDayYearTime => DateTimeFormat::MonthDayYear,
1668            DateTimeFormat::YearMonthDayTime => DateTimeFormat::YearMonthDay,
1669            _ => date_format,
1670        }
1671    }
1672}
1673
1674impl Display for DateTimeFormat {
1675    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
1676        write!(f, "{}", self.to_human_readable_string())
1677    }
1678}
1679
1680#[derive(Serialize, Deserialize, Debug, Clone)]
1681pub struct AppConfig {
1682    pub always_load_last_save: bool,
1683    pub auto_login: bool,
1684    pub date_time_format: DateTimeFormat,
1685    pub default_theme: String,
1686    pub default_view: View,
1687    pub disable_animations: bool,
1688    pub disable_scroll_bar: bool,
1689    pub enable_mouse_support: bool,
1690    pub keybindings: KeyBindings,
1691    pub no_of_boards_to_show: u16,
1692    pub no_of_cards_to_show: u16,
1693    pub date_picker_calender_format: CalenderType,
1694    pub save_directory: PathBuf,
1695    pub save_on_exit: bool,
1696    pub show_line_numbers: bool,
1697    pub tickrate: u16,
1698    pub warning_delta: u16,
1699}
1700
1701impl Default for AppConfig {
1702    fn default() -> Self {
1703        let default_view = DEFAULT_VIEW;
1704        let default_theme = Theme::default();
1705        Self {
1706            always_load_last_save: true,
1707            auto_login: true,
1708            date_time_format: DateTimeFormat::default(),
1709            default_theme: default_theme.name,
1710            default_view,
1711            disable_animations: false,
1712            disable_scroll_bar: false,
1713            enable_mouse_support: true,
1714            keybindings: KeyBindings::default(),
1715            no_of_boards_to_show: DEFAULT_NO_OF_BOARDS_PER_PAGE,
1716            no_of_cards_to_show: DEFAULT_NO_OF_CARDS_PER_BOARD,
1717            date_picker_calender_format: CalenderType::default(),
1718            save_directory: get_default_save_directory(),
1719            save_on_exit: true,
1720            show_line_numbers: true,
1721            tickrate: DEFAULT_TICKRATE,
1722            warning_delta: DEFAULT_CARD_WARNING_DUE_DATE_DAYS,
1723        }
1724    }
1725}
1726
1727impl AppConfig {
1728    pub fn to_view_list(&self) -> Vec<Vec<String>> {
1729        // Custom ordering
1730        let mut view_list = ConfigEnum::iter()
1731            .map(|enum_variant| {
1732                let (value, index) = match enum_variant {
1733                    ConfigEnum::SaveDirectory => {
1734                        (self.save_directory.to_string_lossy().to_string(), 0)
1735                    }
1736                    ConfigEnum::DefaultView => (self.default_view.to_string(), 1),
1737                    ConfigEnum::AlwaysLoadLastSave => (self.always_load_last_save.to_string(), 2),
1738                    ConfigEnum::SaveOnExit => (self.save_on_exit.to_string(), 3),
1739                    ConfigEnum::DisableScrollBar => (self.disable_scroll_bar.to_string(), 4),
1740                    ConfigEnum::DisableAnimations => (self.disable_animations.to_string(), 5),
1741                    ConfigEnum::AutoLogin => (self.auto_login.to_string(), 6),
1742                    ConfigEnum::ShowLineNumbers => (self.show_line_numbers.to_string(), 7),
1743                    ConfigEnum::EnableMouseSupport => (self.enable_mouse_support.to_string(), 8),
1744                    ConfigEnum::WarningDelta => (self.warning_delta.to_string(), 9),
1745                    ConfigEnum::Tickrate => (self.tickrate.to_string(), 10),
1746                    ConfigEnum::NoOfCardsToShow => (self.no_of_cards_to_show.to_string(), 11),
1747                    ConfigEnum::NoOfBoardsToShow => (self.no_of_boards_to_show.to_string(), 12),
1748                    ConfigEnum::DatePickerCalenderFormat => {
1749                        (self.date_picker_calender_format.to_string(), 13)
1750                    }
1751                    ConfigEnum::DefaultTheme => (self.default_theme.clone(), 14),
1752                    ConfigEnum::DateFormat => (self.date_time_format.to_string(), 15),
1753                    ConfigEnum::Keybindings => ("".to_string(), 16),
1754                };
1755                (enum_variant.to_string(), value.to_string(), index)
1756            })
1757            .collect::<Vec<(String, String, usize)>>();
1758
1759        view_list.sort_by(|a, b| a.2.cmp(&b.2));
1760        view_list
1761            .iter()
1762            .map(|(key, value, _)| vec![key.to_owned(), value.to_owned()])
1763            .collect::<Vec<Vec<String>>>()
1764    }
1765
1766    pub fn get_value_as_string(&self, config_enum: ConfigEnum) -> String {
1767        match config_enum {
1768            ConfigEnum::AlwaysLoadLastSave => self.always_load_last_save.to_string(),
1769            ConfigEnum::AutoLogin => self.auto_login.to_string(),
1770            ConfigEnum::DateFormat => self.date_time_format.to_string(),
1771            ConfigEnum::DefaultTheme => self.default_theme.clone(),
1772            ConfigEnum::DefaultView => self.default_view.to_string(),
1773            ConfigEnum::DisableAnimations => self.disable_animations.to_string(),
1774            ConfigEnum::DisableScrollBar => self.disable_scroll_bar.to_string(),
1775            ConfigEnum::EnableMouseSupport => self.enable_mouse_support.to_string(),
1776            ConfigEnum::Keybindings => {
1777                // This should never be called
1778                debug!("Keybindings should not be called from get_value_as_str");
1779                "".to_string()
1780            }
1781            ConfigEnum::NoOfBoardsToShow => self.no_of_boards_to_show.to_string(),
1782            ConfigEnum::NoOfCardsToShow => self.no_of_cards_to_show.to_string(),
1783            ConfigEnum::DatePickerCalenderFormat => self.date_picker_calender_format.to_string(),
1784            ConfigEnum::SaveDirectory => self.save_directory.to_string_lossy().to_string(),
1785            ConfigEnum::SaveOnExit => self.save_on_exit.to_string(),
1786            ConfigEnum::ShowLineNumbers => self.show_line_numbers.to_string(),
1787            ConfigEnum::Tickrate => self.tickrate.to_string(),
1788            ConfigEnum::WarningDelta => self.warning_delta.to_string(),
1789        }
1790    }
1791
1792    pub fn get_toggled_value_as_string(&self, config_enum: ConfigEnum) -> String {
1793        match config_enum {
1794            ConfigEnum::AlwaysLoadLastSave => (!self.always_load_last_save).to_string(),
1795            ConfigEnum::AutoLogin => (!self.auto_login).to_string(),
1796            ConfigEnum::DisableAnimations => (!self.disable_animations).to_string(),
1797            ConfigEnum::DisableScrollBar => (!self.disable_scroll_bar).to_string(),
1798            ConfigEnum::EnableMouseSupport => (!self.enable_mouse_support).to_string(),
1799            ConfigEnum::SaveOnExit => (!self.save_on_exit).to_string(),
1800            ConfigEnum::ShowLineNumbers => (!self.show_line_numbers).to_string(),
1801            ConfigEnum::DatePickerCalenderFormat => match self.date_picker_calender_format {
1802                CalenderType::MondayFirst => CalenderType::SundayFirst.to_string(),
1803                CalenderType::SundayFirst => CalenderType::MondayFirst.to_string(),
1804            },
1805            _ => {
1806                debug!("Invalid config enum to toggle: {}", config_enum);
1807                "".to_string()
1808            }
1809        }
1810    }
1811
1812    pub fn edit_config(app: &mut App, config_enum: ConfigEnum, edited_value: &str) {
1813        let mut config_copy = app.config.clone();
1814        let result = config_enum.edit_config(&mut config_copy, edited_value);
1815        if result.is_ok() {
1816            let write_status = data_handler::write_config(&config_copy);
1817            if write_status.is_ok() {
1818                app.config = config_copy;
1819                app.send_info_toast("Config updated", None);
1820            } else {
1821                app.send_error_toast("Could not write to config file", None);
1822            }
1823        } else {
1824            let error_message = format!("Could not edit config: {}", result.unwrap_err());
1825            error!("{}", error_message);
1826            app.send_error_toast(&error_message, None);
1827        }
1828    }
1829
1830    pub fn edit_keybinding(
1831        &mut self,
1832        key_index: usize,
1833        value: &[Key],
1834    ) -> Result<KeyBindingEnum, String> {
1835        let current_bindings = &self.keybindings;
1836
1837        let mut key_list = vec![];
1838        for (k, v) in current_bindings.iter() {
1839            key_list.push((k, v));
1840        }
1841        if key_index >= key_list.len() {
1842            debug!("Invalid key index: {}", key_index);
1843            error!("Unable to edit keybinding");
1844            return Err("Unable to edit keybinding 😢 ".to_string());
1845        }
1846
1847        key_list.sort_by(|a, b| a.0.to_string().cmp(&b.0.to_string()));
1848
1849        let (key, _) = key_list[key_index];
1850
1851        if !current_bindings.iter().any(|(k, _)| k == key) {
1852            debug!("Invalid key: {}", key);
1853            error!("Unable to edit keybinding");
1854            return Err("Unable to edit keybinding 😢 ".to_string());
1855        }
1856
1857        for new_value in value.iter() {
1858            for (k, v) in current_bindings.iter() {
1859                if v.contains(new_value) && k != key {
1860                    error!("Value {} is already assigned to {}", new_value, k);
1861                    return Err(format!("Value {} is already assigned to {}", new_value, k));
1862                }
1863            }
1864        }
1865
1866        debug!("Editing keybinding: {} to {:?}", key, value);
1867
1868        match key {
1869            KeyBindingEnum::Accept => self.keybindings.accept = value.to_vec(),
1870            KeyBindingEnum::ChangeCardStatusToActive => {
1871                self.keybindings.change_card_status_to_active = value.to_vec();
1872            }
1873            KeyBindingEnum::ChangeCardStatusToCompleted => {
1874                self.keybindings.change_card_status_to_completed = value.to_vec();
1875            }
1876            KeyBindingEnum::ChangeCardStatusToStale => {
1877                self.keybindings.change_card_status_to_stale = value.to_vec();
1878            }
1879            KeyBindingEnum::ChangeCardPriorityToHigh => {
1880                self.keybindings.change_card_priority_to_high = value.to_vec();
1881            }
1882            KeyBindingEnum::ChangeCardPriorityToLow => {
1883                self.keybindings.change_card_priority_to_low = value.to_vec();
1884            }
1885            KeyBindingEnum::ChangeCardPriorityToMedium => {
1886                self.keybindings.change_card_priority_to_medium = value.to_vec();
1887            }
1888            KeyBindingEnum::ClearAllToasts => {
1889                self.keybindings.clear_all_toasts = value.to_vec();
1890            }
1891            KeyBindingEnum::DeleteBoard => {
1892                self.keybindings.delete_board = value.to_vec();
1893            }
1894            KeyBindingEnum::DeleteCard => {
1895                self.keybindings.delete_card = value.to_vec();
1896            }
1897            KeyBindingEnum::Down => {
1898                self.keybindings.down = value.to_vec();
1899            }
1900            KeyBindingEnum::GoToMainMenu => {
1901                self.keybindings.go_to_main_menu = value.to_vec();
1902            }
1903            KeyBindingEnum::GoToPreviousViewOrCancel => {
1904                self.keybindings.go_to_previous_view_or_cancel = value.to_vec();
1905            }
1906            KeyBindingEnum::HideUiElement => {
1907                self.keybindings.hide_ui_element = value.to_vec();
1908            }
1909            KeyBindingEnum::Left => {
1910                self.keybindings.left = value.to_vec();
1911            }
1912            KeyBindingEnum::MoveCardDown => {
1913                self.keybindings.move_card_down = value.to_vec();
1914            }
1915            KeyBindingEnum::MoveCardLeft => {
1916                self.keybindings.move_card_left = value.to_vec();
1917            }
1918            KeyBindingEnum::MoveCardRight => {
1919                self.keybindings.move_card_right = value.to_vec();
1920            }
1921            KeyBindingEnum::MoveCardUp => {
1922                self.keybindings.move_card_up = value.to_vec();
1923            }
1924            KeyBindingEnum::NewBoard => {
1925                self.keybindings.new_board = value.to_vec();
1926            }
1927            KeyBindingEnum::NewCard => {
1928                self.keybindings.new_card = value.to_vec();
1929            }
1930            KeyBindingEnum::NextFocus => {
1931                self.keybindings.next_focus = value.to_vec();
1932            }
1933            KeyBindingEnum::OpenConfigMenu => {
1934                self.keybindings.open_config_menu = value.to_vec();
1935            }
1936            KeyBindingEnum::PrvFocus => {
1937                self.keybindings.prv_focus = value.to_vec();
1938            }
1939            KeyBindingEnum::Quit => {
1940                self.keybindings.quit = value.to_vec();
1941            }
1942            KeyBindingEnum::Redo => {
1943                self.keybindings.redo = value.to_vec();
1944            }
1945            KeyBindingEnum::ResetUI => {
1946                self.keybindings.reset_ui = value.to_vec();
1947            }
1948            KeyBindingEnum::Right => {
1949                self.keybindings.right = value.to_vec();
1950            }
1951            KeyBindingEnum::SaveState => {
1952                self.keybindings.save_state = value.to_vec();
1953            }
1954            KeyBindingEnum::StopUserInput => {
1955                self.keybindings.stop_user_input = value.to_vec();
1956            }
1957            KeyBindingEnum::TakeUserInput => {
1958                self.keybindings.take_user_input = value.to_vec();
1959            }
1960            KeyBindingEnum::ToggleCommandPalette => {
1961                self.keybindings.toggle_command_palette = value.to_vec();
1962            }
1963            KeyBindingEnum::Undo => {
1964                self.keybindings.undo = value.to_vec();
1965            }
1966            KeyBindingEnum::Up => {
1967                self.keybindings.up = value.to_vec();
1968            }
1969        }
1970        Ok(key)
1971    }
1972
1973    fn get_bool_or_default(
1974        serde_json_object: &serde_json::Value,
1975        config_enum: ConfigEnum,
1976        default: bool,
1977    ) -> bool {
1978        match serde_json_object[config_enum.to_json_key()].as_bool() {
1979            Some(value) => value,
1980            None => {
1981                error!(
1982                    "{} is not a boolean (true/false), Resetting to default value",
1983                    config_enum.to_json_key()
1984                );
1985                default
1986            }
1987        }
1988    }
1989
1990    fn get_u16_or_default(
1991        serde_json_object: &serde_json::Value,
1992        config_enum: ConfigEnum,
1993        default: u16,
1994        min: Option<u16>,
1995        max: Option<u16>,
1996    ) -> u16 {
1997        match serde_json_object[config_enum.to_json_key()].as_u64() {
1998            Some(value) => {
1999                if let Some(min) = min {
2000                    if value < min as u64 {
2001                        error!(
2002                            "Invalid value: {} for {}, It must be greater than {}, Resetting to default value",
2003                            value, config_enum.to_json_key(), min
2004                        );
2005                        return default;
2006                    }
2007                }
2008                if let Some(max) = max {
2009                    if value > max as u64 {
2010                        error!(
2011                            "Invalid value: {} for {}, It must be less than {}, Resetting to default value",
2012                            value, config_enum.to_json_key(), max
2013                        );
2014                        return default;
2015                    }
2016                }
2017                value as u16
2018            }
2019            None => {
2020                error!(
2021                    "{} is not a number, Resetting to default value",
2022                    config_enum.to_json_key()
2023                );
2024                default
2025            }
2026        }
2027    }
2028
2029    fn handle_invalid_keybinding(key: &str) {
2030        error!(
2031            "Invalid keybinding for key {}, Resetting to default keybinding",
2032            key
2033        );
2034    }
2035
2036    fn json_config_keybindings_checker(serde_json_object: &Value) -> KeyBindings {
2037        if let Some(keybindings) = serde_json_object["keybindings"].as_object() {
2038            let mut default_keybindings = KeyBindings::default();
2039            for (key, value) in keybindings.iter() {
2040                let mut keybindings = vec![];
2041                if let Some(value_array) = value.as_array() {
2042                    for keybinding_value in value_array {
2043                        if let Some(keybinding_value_str) = keybinding_value.as_str() {
2044                            let keybinding_value = Key::from(keybinding_value_str);
2045                            if keybinding_value != Key::Unknown {
2046                                keybindings.push(keybinding_value);
2047                            } else {
2048                                Self::handle_invalid_keybinding(key);
2049                            }
2050                        } else if let Some(keybinding_value_obj) = keybinding_value.as_object() {
2051                            let keybinding_value = Key::from(keybinding_value_obj);
2052                            if keybinding_value != Key::Unknown {
2053                                keybindings.push(keybinding_value);
2054                            } else {
2055                                Self::handle_invalid_keybinding(key);
2056                            }
2057                        } else {
2058                            Self::handle_invalid_keybinding(key);
2059                        }
2060                    }
2061                    if keybindings.is_empty() {
2062                        Self::handle_invalid_keybinding(key);
2063                    } else {
2064                        default_keybindings.edit_keybinding(key, keybindings);
2065                    }
2066                } else {
2067                    Self::handle_invalid_keybinding(key);
2068                }
2069            }
2070            default_keybindings
2071        } else {
2072            KeyBindings::default()
2073        }
2074    }
2075
2076    pub fn from_json_string(json_string: &str) -> Result<Self, String> {
2077        let root = serde_json::from_str(json_string);
2078        if root.is_err() {
2079            error!("Unable to recover old config. Resetting to default config");
2080            debug!("Error: {}", root.unwrap_err());
2081            return Err("Unable to recover old config. Resetting to default config".to_string());
2082        }
2083        let serde_json_object: Value = root.unwrap();
2084        let default_config = AppConfig::default();
2085        let save_directory =
2086            match serde_json_object[ConfigEnum::SaveDirectory.to_json_key()].as_str() {
2087                Some(path) => {
2088                    let path = PathBuf::from(path);
2089                    if path.exists() {
2090                        path
2091                    } else {
2092                        error!(
2093                            "Invalid path: {}, Resetting to default save directory",
2094                            path.to_str().unwrap()
2095                        );
2096                        default_config.save_directory
2097                    }
2098                }
2099                None => {
2100                    error!("Save Directory is not a string, Resetting to default save directory");
2101                    default_config.save_directory
2102                }
2103            };
2104        let default_view = match serde_json_object[ConfigEnum::DefaultView.to_json_key()].as_str() {
2105            Some(view) => {
2106                let view = View::from_str(view);
2107                if let Ok(view) = view {
2108                    view
2109                } else {
2110                    error!("Invalid View: {:?}, Resetting to default View", view);
2111                    default_config.default_view
2112                }
2113            }
2114            None => {
2115                error!("Default View is not a string, Resetting to default View");
2116                default_config.default_view
2117            }
2118        };
2119        let keybindings = AppConfig::json_config_keybindings_checker(&serde_json_object);
2120        let always_load_last_save = AppConfig::get_bool_or_default(
2121            &serde_json_object,
2122            ConfigEnum::AlwaysLoadLastSave,
2123            default_config.always_load_last_save,
2124        );
2125        let save_on_exit = AppConfig::get_bool_or_default(
2126            &serde_json_object,
2127            ConfigEnum::SaveOnExit,
2128            default_config.save_on_exit,
2129        );
2130        let disable_scroll_bar = AppConfig::get_bool_or_default(
2131            &serde_json_object,
2132            ConfigEnum::DisableScrollBar,
2133            default_config.disable_scroll_bar,
2134        );
2135        let auto_login = AppConfig::get_bool_or_default(
2136            &serde_json_object,
2137            ConfigEnum::AutoLogin,
2138            default_config.auto_login,
2139        );
2140        let show_line_numbers = AppConfig::get_bool_or_default(
2141            &serde_json_object,
2142            ConfigEnum::ShowLineNumbers,
2143            default_config.show_line_numbers,
2144        );
2145        let disable_animations = AppConfig::get_bool_or_default(
2146            &serde_json_object,
2147            ConfigEnum::DisableAnimations,
2148            default_config.disable_animations,
2149        );
2150        let enable_mouse_support = AppConfig::get_bool_or_default(
2151            &serde_json_object,
2152            ConfigEnum::EnableMouseSupport,
2153            default_config.enable_mouse_support,
2154        );
2155        let warning_delta = AppConfig::get_u16_or_default(
2156            &serde_json_object,
2157            ConfigEnum::WarningDelta,
2158            default_config.warning_delta,
2159            Some(1),
2160            None,
2161        );
2162        let tickrate = AppConfig::get_u16_or_default(
2163            &serde_json_object,
2164            ConfigEnum::Tickrate,
2165            default_config.tickrate,
2166            Some(MIN_TICKRATE),
2167            Some(MAX_TICKRATE),
2168        );
2169        let no_of_cards_to_show = AppConfig::get_u16_or_default(
2170            &serde_json_object,
2171            ConfigEnum::NoOfCardsToShow,
2172            default_config.no_of_cards_to_show,
2173            Some(MIN_NO_CARDS_PER_BOARD),
2174            Some(MAX_NO_CARDS_PER_BOARD),
2175        );
2176        let no_of_boards_to_show = AppConfig::get_u16_or_default(
2177            &serde_json_object,
2178            ConfigEnum::NoOfBoardsToShow,
2179            default_config.no_of_boards_to_show,
2180            Some(MIN_NO_BOARDS_PER_PAGE),
2181            Some(MAX_NO_BOARDS_PER_PAGE),
2182        );
2183        let default_theme = match serde_json_object[ConfigEnum::DefaultTheme.to_json_key()].as_str()
2184        {
2185            Some(default_theme) => default_theme.to_string(),
2186            None => {
2187                error!("Default Theme is not a string, Resetting to default theme");
2188                default_config.default_theme
2189            }
2190        };
2191        let date_format = match serde_json_object[ConfigEnum::DateFormat.to_json_key()].as_str() {
2192            Some(date_format) => match DateTimeFormat::from_str(date_format) {
2193                Ok(date_format) => date_format,
2194                Err(date_format_parse_error) => {
2195                    error!(
2196                        "Invalid date format: {}, Resetting to default date format",
2197                        date_format
2198                    );
2199                    debug!("Error: {}", date_format_parse_error);
2200                    default_config.date_time_format
2201                }
2202            },
2203            None => {
2204                error!("Date Format is not a string, Resetting to default date format");
2205                default_config.date_time_format
2206            }
2207        };
2208        let date_picker_calender_format =
2209            match serde_json_object[ConfigEnum::DatePickerCalenderFormat.to_json_key()].as_str() {
2210                Some(calender_format) => match CalenderType::from_str(calender_format) {
2211                    Ok(calender_format) => calender_format,
2212                    Err(calender_format_parse_error) => {
2213                        error!(
2214                            "Invalid calender format: {}, Resetting to default calender format",
2215                            calender_format
2216                        );
2217                        debug!("Error: {}", calender_format_parse_error);
2218                        CalenderType::default()
2219                    }
2220                },
2221                None => {
2222                    error!("Calender Format is not a string, Resetting to default calender format");
2223                    CalenderType::default()
2224                }
2225            };
2226        Ok(Self {
2227            save_directory,
2228            default_view,
2229            always_load_last_save,
2230            save_on_exit,
2231            disable_scroll_bar,
2232            auto_login,
2233            warning_delta,
2234            keybindings,
2235            tickrate,
2236            no_of_cards_to_show,
2237            no_of_boards_to_show,
2238            date_picker_calender_format,
2239            enable_mouse_support,
2240            default_theme,
2241            date_time_format: date_format,
2242            show_line_numbers,
2243            disable_animations,
2244        })
2245    }
2246}
2247
2248#[derive(PartialEq, Copy, Clone, EnumIter)]
2249pub enum ConfigEnum {
2250    AlwaysLoadLastSave,
2251    AutoLogin,
2252    DateFormat,
2253    DefaultTheme,
2254    DefaultView,
2255    DisableAnimations,
2256    DisableScrollBar,
2257    EnableMouseSupport,
2258    Keybindings,
2259    NoOfBoardsToShow,
2260    NoOfCardsToShow,
2261    DatePickerCalenderFormat,
2262    SaveDirectory,
2263    SaveOnExit,
2264    ShowLineNumbers,
2265    Tickrate,
2266    WarningDelta,
2267}
2268
2269impl fmt::Display for ConfigEnum {
2270    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2271        match *self {
2272            ConfigEnum::AlwaysLoadLastSave => write!(f, "Auto Load Last Save"),
2273            ConfigEnum::AutoLogin => write!(f, "Auto Login"),
2274            ConfigEnum::DateFormat => write!(f, "Date Format"),
2275            ConfigEnum::DefaultTheme => write!(f, "Default Theme"),
2276            ConfigEnum::DefaultView => write!(f, "Select Default View"),
2277            ConfigEnum::DisableAnimations => write!(f, "Disable Animations"),
2278            ConfigEnum::DisableScrollBar => write!(f, "Disable Scroll Bar"),
2279            ConfigEnum::EnableMouseSupport => write!(f, "Enable Mouse Support"),
2280            ConfigEnum::Keybindings => write!(f, "Edit Keybindings"),
2281            ConfigEnum::NoOfBoardsToShow => write!(f, "Number of Boards to Show"),
2282            ConfigEnum::NoOfCardsToShow => write!(f, "Number of Cards to Show"),
2283            ConfigEnum::DatePickerCalenderFormat => write!(f, "Date Picker Calender Format"),
2284            ConfigEnum::SaveDirectory => write!(f, "Save Directory"),
2285            ConfigEnum::SaveOnExit => write!(f, "Auto Save on Exit"),
2286            ConfigEnum::ShowLineNumbers => write!(f, "Show Line Numbers"),
2287            ConfigEnum::Tickrate => write!(f, "Tickrate"),
2288            ConfigEnum::WarningDelta => write!(f, "Number of Days to Warn Before Due Date"),
2289        }
2290    }
2291}
2292
2293impl FromStr for ConfigEnum {
2294    type Err = String;
2295    fn from_str(s: &str) -> Result<Self, Self::Err> {
2296        match s {
2297            "Auto Load Last Save" => Ok(ConfigEnum::AlwaysLoadLastSave),
2298            "Auto Login" => Ok(ConfigEnum::AutoLogin),
2299            "Auto Save on Exit" => Ok(ConfigEnum::SaveOnExit),
2300            "Date Format" => Ok(ConfigEnum::DateFormat),
2301            "Default Theme" => Ok(ConfigEnum::DefaultTheme),
2302            "Disable Animations" => Ok(ConfigEnum::DisableAnimations),
2303            "Disable Scroll Bar" => Ok(ConfigEnum::DisableScrollBar),
2304            "Edit Keybindings" => Ok(ConfigEnum::Keybindings),
2305            "Enable Mouse Support" => Ok(ConfigEnum::EnableMouseSupport),
2306            "Number of Boards to Show" => Ok(ConfigEnum::NoOfBoardsToShow),
2307            "Number of Cards to Show" => Ok(ConfigEnum::NoOfCardsToShow),
2308            "Date Picker Calender Format" => Ok(ConfigEnum::DatePickerCalenderFormat),
2309            "Number of Days to Warn Before Due Date" => Ok(ConfigEnum::WarningDelta),
2310            "Save Directory" => Ok(ConfigEnum::SaveDirectory),
2311            "Select Default View" => Ok(ConfigEnum::DefaultView),
2312            "Show Line Numbers" => Ok(ConfigEnum::ShowLineNumbers),
2313            "Tickrate" => Ok(ConfigEnum::Tickrate),
2314            _ => Err(format!("Invalid ConfigEnum: {}", s)),
2315        }
2316    }
2317}
2318
2319impl ConfigEnum {
2320    pub fn to_json_key(&self) -> &str {
2321        match self {
2322            ConfigEnum::AlwaysLoadLastSave => "always_load_last_save",
2323            ConfigEnum::AutoLogin => "auto_login",
2324            ConfigEnum::DateFormat => "date_format",
2325            ConfigEnum::DefaultTheme => "default_theme",
2326            ConfigEnum::DefaultView => "default_view",
2327            ConfigEnum::DisableAnimations => "disable_animations",
2328            ConfigEnum::DisableScrollBar => "disable_scroll_bar",
2329            ConfigEnum::EnableMouseSupport => "enable_mouse_support",
2330            ConfigEnum::Keybindings => "keybindings",
2331            ConfigEnum::NoOfBoardsToShow => "no_of_boards_to_show",
2332            ConfigEnum::NoOfCardsToShow => "no_of_cards_to_show",
2333            ConfigEnum::DatePickerCalenderFormat => "date_picker_calender_format",
2334            ConfigEnum::SaveDirectory => "save_directory",
2335            ConfigEnum::SaveOnExit => "save_on_exit",
2336            ConfigEnum::ShowLineNumbers => "show_line_numbers",
2337            ConfigEnum::Tickrate => "tickrate",
2338            ConfigEnum::WarningDelta => "warning_delta",
2339        }
2340    }
2341
2342    pub fn validate_value(&self, value: &str) -> Result<(), String> {
2343        match self {
2344            ConfigEnum::SaveDirectory => {
2345                let path = PathBuf::from(value);
2346                if path.try_exists().is_ok() && path.try_exists().unwrap() && path.is_dir() {
2347                    Ok(())
2348                } else {
2349                    Err(format!("Invalid path: {}", value))
2350                }
2351            }
2352            ConfigEnum::DefaultView => {
2353                let view = View::from_string(value);
2354                if view.is_some() {
2355                    Ok(())
2356                } else {
2357                    Err(format!("Invalid View: {}", value))
2358                }
2359            }
2360            ConfigEnum::AlwaysLoadLastSave
2361            | ConfigEnum::AutoLogin
2362            | ConfigEnum::DisableAnimations
2363            | ConfigEnum::DisableScrollBar
2364            | ConfigEnum::EnableMouseSupport
2365            | ConfigEnum::SaveOnExit
2366            | ConfigEnum::ShowLineNumbers => {
2367                let check = value.parse::<bool>();
2368                if check.is_ok() {
2369                    Ok(())
2370                } else {
2371                    Err(format!("Invalid boolean: {}", value))
2372                }
2373            }
2374            ConfigEnum::NoOfBoardsToShow
2375            | ConfigEnum::NoOfCardsToShow
2376            | ConfigEnum::Tickrate
2377            | ConfigEnum::WarningDelta => {
2378                let min_value = match self {
2379                    ConfigEnum::WarningDelta => MIN_WARNING_DUE_DATE_DAYS,
2380                    ConfigEnum::Tickrate => MIN_TICKRATE,
2381                    ConfigEnum::NoOfCardsToShow => MIN_NO_CARDS_PER_BOARD,
2382                    ConfigEnum::NoOfBoardsToShow => MIN_NO_BOARDS_PER_PAGE,
2383                    _ => 0,
2384                };
2385                let max_value = match self {
2386                    ConfigEnum::WarningDelta => MAX_WARNING_DUE_DATE_DAYS,
2387                    ConfigEnum::Tickrate => MAX_TICKRATE,
2388                    ConfigEnum::NoOfCardsToShow => MAX_NO_CARDS_PER_BOARD,
2389                    ConfigEnum::NoOfBoardsToShow => MAX_NO_BOARDS_PER_PAGE,
2390                    _ => 0,
2391                };
2392                let check = value.parse::<u16>();
2393                if check.is_ok() {
2394                    let value = check.unwrap();
2395                    if value >= min_value && value <= max_value {
2396                        Ok(())
2397                    } else {
2398                        Err(format!(
2399                            "Invalid number: {}, It must be between {} and {}",
2400                            value, min_value, max_value
2401                        ))
2402                    }
2403                } else {
2404                    Err(format!("Invalid number: {}", value))
2405                }
2406            }
2407            ConfigEnum::DefaultTheme => {
2408                // TODO: check if theme exists
2409                Ok(())
2410            }
2411            ConfigEnum::DateFormat => {
2412                let date_format = DateTimeFormat::from_human_readable_string(value);
2413                if date_format.is_some() {
2414                    Ok(())
2415                } else {
2416                    Err(format!("Invalid DateFormat: {}", value))
2417                }
2418            }
2419            ConfigEnum::DatePickerCalenderFormat => {
2420                let calender_format = CalenderType::try_from(value);
2421                if calender_format.is_ok() {
2422                    Ok(())
2423                } else {
2424                    Err(format!("Invalid CalenderFormat: {}", value))
2425                }
2426            }
2427            ConfigEnum::Keybindings => {
2428                debug!("Keybindings should not be called from validate_value");
2429                // Keybindings are handled separately
2430                Ok(())
2431            }
2432        }
2433    }
2434
2435    pub fn edit_config(&self, config: &mut AppConfig, value: &str) -> Result<(), String> {
2436        let value = value.trim();
2437        self.validate_value(value)?;
2438        // No need to be safe, since the value has been validated
2439        match self {
2440            ConfigEnum::SaveDirectory => {
2441                config.save_directory = PathBuf::from(value);
2442            }
2443            ConfigEnum::DefaultView => {
2444                config.default_view = View::from_string(value).unwrap();
2445            }
2446            ConfigEnum::AlwaysLoadLastSave => {
2447                config.always_load_last_save = value.parse::<bool>().unwrap();
2448            }
2449            ConfigEnum::SaveOnExit => {
2450                config.save_on_exit = value.parse::<bool>().unwrap();
2451            }
2452            ConfigEnum::DisableScrollBar => {
2453                config.disable_scroll_bar = value.parse::<bool>().unwrap();
2454            }
2455            ConfigEnum::AutoLogin => {
2456                config.auto_login = value.parse::<bool>().unwrap();
2457            }
2458            ConfigEnum::ShowLineNumbers => {
2459                config.show_line_numbers = value.parse::<bool>().unwrap();
2460            }
2461            ConfigEnum::DisableAnimations => {
2462                config.disable_animations = value.parse::<bool>().unwrap();
2463            }
2464            ConfigEnum::EnableMouseSupport => {
2465                config.enable_mouse_support = value.parse::<bool>().unwrap();
2466            }
2467            ConfigEnum::WarningDelta => {
2468                config.warning_delta = value.parse::<u16>().unwrap();
2469            }
2470            ConfigEnum::Tickrate => {
2471                config.tickrate = value.parse::<u16>().unwrap();
2472            }
2473            ConfigEnum::NoOfCardsToShow => {
2474                config.no_of_cards_to_show = value.parse::<u16>().unwrap();
2475            }
2476            ConfigEnum::NoOfBoardsToShow => {
2477                config.no_of_boards_to_show = value.parse::<u16>().unwrap();
2478            }
2479            ConfigEnum::DefaultTheme => {
2480                config.default_theme = value.to_string();
2481            }
2482            ConfigEnum::DateFormat => {
2483                config.date_time_format =
2484                    DateTimeFormat::from_human_readable_string(value).unwrap();
2485            }
2486            ConfigEnum::DatePickerCalenderFormat => {
2487                config.date_picker_calender_format = CalenderType::try_from(value).unwrap();
2488            }
2489            ConfigEnum::Keybindings => {
2490                debug!("Keybindings should not be called from edit_config");
2491                // Keybindings are handled separately
2492            }
2493        }
2494        Ok(())
2495    }
2496}
2497
2498pub async fn handle_exit(app: &mut App<'_>) -> AppReturn {
2499    if app.config.save_on_exit {
2500        app.dispatch(IoEvent::AutoSave).await;
2501    }
2502    AppReturn::Exit
2503}