From 7c4d33654fb37200905c06c198f427545fedd461 Mon Sep 17 00:00:00 2001
From: mo8it <mo8it@proton.me>
Date: Mon, 8 Apr 2024 02:41:48 +0200
Subject: [PATCH] Implement done/pending filters

---
 src/list.rs       | 30 +++++++++++++++++++++++++++---
 src/list/state.rs | 40 ++++++++++++++++++++++++++++++++--------
 2 files changed, 59 insertions(+), 11 deletions(-)

diff --git a/src/list.rs b/src/list.rs
index 3d91b8ae..d7fa05f1 100644
--- a/src/list.rs
+++ b/src/list.rs
@@ -11,7 +11,7 @@ mod state;
 
 use crate::{exercise::Exercise, state_file::StateFile};
 
-use self::state::UiState;
+use self::state::{Filter, UiState};
 
 pub fn list(state_file: &mut StateFile, exercises: &[Exercise]) -> Result<()> {
     let mut stdout = io::stdout().lock();
@@ -50,20 +50,44 @@ pub fn list(state_file: &mut StateFile, exercises: &[Exercise]) -> Result<()> {
             KeyCode::Up | KeyCode::Char('k') => ui_state.select_previous(),
             KeyCode::Home | KeyCode::Char('g') => ui_state.select_first(),
             KeyCode::End | KeyCode::Char('G') => ui_state.select_last(),
+            KeyCode::Char('d') => {
+                let message = if ui_state.filter == Filter::Done {
+                    ui_state.filter = Filter::None;
+                    "Disabled filter DONE"
+                } else {
+                    ui_state.filter = Filter::Done;
+                    "Enabled filter DONE │ Press d again to disable the filter"
+                };
+
+                ui_state = ui_state.with_updated_rows(state_file);
+                ui_state.message.push_str(message);
+            }
+            KeyCode::Char('p') => {
+                let message = if ui_state.filter == Filter::Pending {
+                    ui_state.filter = Filter::None;
+                    "Disabled filter PENDING"
+                } else {
+                    ui_state.filter = Filter::Pending;
+                    "Enabled filter PENDING │ Press p again to disable the filter"
+                };
+
+                ui_state = ui_state.with_updated_rows(state_file);
+                ui_state.message.push_str(message);
+            }
             KeyCode::Char('r') => {
                 let selected = ui_state.selected();
                 let exercise = &exercises[selected];
                 exercise.reset()?;
                 state_file.reset(selected)?;
 
-                ui_state.table = ui_state.table.rows(UiState::rows(state_file, exercises));
+                ui_state = ui_state.with_updated_rows(state_file);
                 ui_state
                     .message
                     .write_fmt(format_args!("The exercise {exercise} has been reset!"))?;
             }
             KeyCode::Char('c') => {
                 state_file.set_next_exercise_ind(ui_state.selected())?;
-                ui_state.table = ui_state.table.rows(UiState::rows(state_file, exercises));
+                ui_state = ui_state.with_updated_rows(state_file);
             }
             _ => (),
         }
diff --git a/src/list/state.rs b/src/list/state.rs
index d2ade97e..30567d1c 100644
--- a/src/list/state.rs
+++ b/src/list/state.rs
@@ -8,18 +8,28 @@ use ratatui::{
 
 use crate::{exercise::Exercise, state_file::StateFile};
 
+#[derive(Copy, Clone, PartialEq, Eq)]
+pub enum Filter {
+    Done,
+    Pending,
+    None,
+}
+
 pub struct UiState<'a> {
     pub table: Table<'a>,
     pub message: String,
+    pub filter: Filter,
+    exercises: &'a [Exercise],
     selected: usize,
     table_state: TableState,
     last_ind: usize,
 }
 
 impl<'a> UiState<'a> {
-    pub fn rows<'s, 'i>(
+    fn rows<'s, 'i>(
         state_file: &'s StateFile,
         exercises: &'a [Exercise],
+        filter: Filter,
     ) -> impl Iterator<Item = Row<'a>> + 'i
     where
         's: 'i,
@@ -27,30 +37,41 @@ impl<'a> UiState<'a> {
     {
         exercises
             .iter()
-            .zip(state_file.progress())
+            .zip(state_file.progress().iter().copied())
             .enumerate()
-            .map(|(ind, (exercise, done))| {
+            .filter_map(move |(ind, (exercise, done))| {
+                match (filter, done) {
+                    (Filter::Done, false) | (Filter::Pending, true) => return None,
+                    _ => (),
+                }
+
                 let next = if ind == state_file.next_exercise_ind() {
                     ">>>>".bold().red()
                 } else {
                     Span::default()
                 };
 
-                let exercise_state = if *done {
+                let exercise_state = if done {
                     "DONE".green()
                 } else {
                     "PENDING".yellow()
                 };
 
-                Row::new([
+                Some(Row::new([
                     next,
                     exercise_state,
                     Span::raw(&exercise.name),
                     Span::raw(exercise.path.to_string_lossy()),
-                ])
+                ]))
             })
     }
 
+    pub fn with_updated_rows(mut self, state_file: &StateFile) -> Self {
+        let rows = Self::rows(state_file, self.exercises, self.filter);
+        self.table = self.table.rows(rows);
+        self
+    }
+
     pub fn new(state_file: &StateFile, exercises: &'a [Exercise]) -> Self {
         let header = Row::new(["Next", "State", "Name", "Path"]);
 
@@ -67,7 +88,8 @@ impl<'a> UiState<'a> {
             Constraint::Fill(1),
         ];
 
-        let rows = Self::rows(state_file, exercises);
+        let filter = Filter::None;
+        let rows = Self::rows(state_file, exercises, filter);
 
         let table = Table::new(rows, widths)
             .header(header)
@@ -84,10 +106,12 @@ impl<'a> UiState<'a> {
 
         Self {
             table,
+            message: String::with_capacity(128),
+            filter,
+            exercises,
             selected,
             table_state,
             last_ind: exercises.len() - 1,
-            message: String::with_capacity(128),
         }
     }