vault_tasks_time_management/
lib.rs

1use std::time::Duration;
2
3use pomodoro::Pomodoro;
4use time_management_technique::TimeManagementTechnique;
5
6pub mod flow_time;
7pub mod pomodoro;
8pub mod time_management_technique;
9
10#[derive(Debug, PartialEq, Clone)]
11pub enum State {
12    Focus(Option<Duration>),
13    Break(Option<Duration>),
14}
15#[derive(Debug)]
16/// Provides tracking methods using a generic `TimeTrackingTechnique`
17pub struct TimeManagementEngine {
18    pub mode: Box<dyn TimeManagementTechnique>,
19    pub state: Option<State>,
20}
21impl Default for TimeManagementEngine {
22    fn default() -> Self {
23        Self {
24            mode: Box::new(Pomodoro::classic_pomodoro()),
25            state: None,
26        }
27    }
28}
29impl TimeManagementEngine {
30    /// Creates a new [`TimeTrackingEngine<T>`].
31    pub fn new(technique: Box<dyn TimeManagementTechnique>) -> Self {
32        Self {
33            mode: technique,
34            state: None,
35        }
36    }
37
38    /// Returns the next state of the time tracking engine.
39    /// # Argument
40    /// - `time_spent: Duration`: The duration of the previous session.
41    /// # Returns
42    /// - `Option<Duration>`: Whether there is or not an explicit duration for the next session
43    /// - `TimeManagementEngine<T>`: The next state of the engine
44    pub fn switch(&mut self, time_spent: Duration) -> State {
45        let new_state = self.mode.switch(&self.state, time_spent);
46        self.state = Some(new_state.clone());
47        new_state
48    }
49}
50
51#[cfg(test)]
52mod tests {
53    use color_eyre::eyre::Result;
54
55    use crate::{flow_time::FlowTime, pomodoro::Pomodoro, State, TimeManagementEngine};
56
57    use std::time::Duration;
58
59    #[test]
60    fn test_run_pomodoro() {
61        let mut time_tracker = TimeManagementEngine::new(Box::new(Pomodoro::classic_pomodoro()));
62        let focus_time = Duration::from_secs(60 * 25);
63        let short_break_time = Duration::from_secs(60 * 5);
64        assert!(time_tracker.state.is_none());
65
66        let to_spend_opt = time_tracker.switch(Duration::default());
67        assert!(time_tracker.state.is_some());
68        assert_eq!(
69            time_tracker.state.clone().unwrap(),
70            State::Focus(Some(focus_time))
71        );
72        assert_eq!(State::Focus(Some(focus_time)), to_spend_opt);
73
74        let to_spend_opt = time_tracker.switch(Duration::default());
75        assert!(time_tracker.state.is_some());
76        assert_eq!(
77            time_tracker.state.clone().unwrap(),
78            State::Break(Some(short_break_time))
79        );
80        assert_eq!(State::Break(Some(short_break_time)), to_spend_opt);
81    }
82    #[test]
83    fn test_full_run_pomodoro() {
84        let mut time_tracker = TimeManagementEngine::new(Box::new(Pomodoro::classic_pomodoro()));
85        assert!(time_tracker.state.is_none());
86
87        let mut to_spend_opt = State::Focus(None);
88
89        for _i in 0..2 {
90            // (Focus -> Break) 3 times
91            for _j in 0..(3 * 2) {
92                let to_spend_opt2 = time_tracker.switch(Duration::from_secs(0));
93                to_spend_opt = to_spend_opt2;
94            }
95
96            assert!(time_tracker.state.is_some());
97            assert_eq!(time_tracker.state.clone().unwrap(), to_spend_opt);
98        }
99    }
100    #[test]
101    fn test_run_flowtime() -> Result<()> {
102        let break_factor = 5;
103        let mut time_tracker = TimeManagementEngine::new(Box::new(FlowTime::new(break_factor)?));
104
105        assert!(time_tracker.state.is_none());
106
107        let focus_time = Duration::from_secs(25);
108        let break_time = focus_time / break_factor;
109
110        let to_spend_opt = time_tracker.switch(Duration::from_secs(0));
111
112        assert_eq!(State::Focus(None), to_spend_opt);
113        assert!(time_tracker.state.is_some());
114        assert_eq!(time_tracker.state.clone().unwrap(), State::Focus(None));
115
116        let to_spend_opt = time_tracker.switch(focus_time);
117        assert!(time_tracker.state.is_some());
118        assert_eq!(
119            time_tracker.state.clone().unwrap(),
120            State::Break(Some(break_time))
121        );
122        assert_eq!(State::Break(Some(break_time)), to_spend_opt);
123        Ok(())
124    }
125    #[test]
126    fn test_run_flowtime_excess_break_time() -> Result<()> {
127        let break_factor = 5;
128        let mut time_tracker = TimeManagementEngine::new(Box::new(FlowTime::new(break_factor)?));
129
130        assert!(time_tracker.state.is_none());
131
132        let focus_time = Duration::from_secs(25);
133        let break_time = focus_time / break_factor;
134
135        let to_spend_opt = time_tracker.switch(Duration::from_secs(0));
136
137        assert_eq!(State::Focus(None), to_spend_opt);
138        assert!(time_tracker.state.is_some());
139        assert_eq!(time_tracker.state.clone().unwrap(), State::Focus(None));
140
141        let to_spend_opt = time_tracker.switch(focus_time);
142        assert!(time_tracker.state.is_some());
143        assert_eq!(
144            time_tracker.state.clone().unwrap(),
145            State::Break(Some(break_time))
146        );
147        assert_eq!(State::Break(Some(break_time)), to_spend_opt);
148
149        // Break time lasted 2s instead of 5s
150        let break_time_skipped = Duration::from_secs(3);
151        time_tracker.switch(break_time - break_time_skipped);
152        let to_spend_opt = time_tracker.switch(focus_time);
153        assert!(time_tracker.state.is_some());
154        assert_eq!(
155            time_tracker.state.clone().unwrap(),
156            State::Break(Some(break_time + break_time_skipped))
157        );
158        assert_eq!(
159            State::Break(Some(break_time + break_time_skipped)),
160            to_spend_opt
161        );
162
163        // Ensures we return to normal cycle
164        let to_spend_opt = time_tracker.switch(break_time + break_time_skipped);
165        assert_eq!(State::Focus(None), to_spend_opt);
166        assert!(time_tracker.state.is_some());
167        assert_eq!(time_tracker.state.clone().unwrap(), State::Focus(None));
168
169        // Break time is normal
170        let to_spend_opt = time_tracker.switch(focus_time);
171        assert!(time_tracker.state.is_some());
172        assert_eq!(
173            time_tracker.state.clone().unwrap(),
174            State::Break(Some(break_time))
175        );
176        assert_eq!(State::Break(Some(break_time)), to_spend_opt);
177        Ok(())
178    }
179    #[test]
180    fn test_run_pomodoro_excess_break_time() {
181        let mut time_tracker = TimeManagementEngine::new(Box::new(Pomodoro::classic_pomodoro()));
182
183        assert!(time_tracker.state.is_none());
184
185        let focus_time = Duration::from_secs(1500);
186        let break_time = Duration::from_secs(1500 / 5);
187
188        // Init -> Focus
189        let to_spend_opt = time_tracker.switch(Duration::from_secs(0));
190
191        assert_eq!(State::Focus(Some(focus_time)), to_spend_opt);
192        assert!(time_tracker.state.is_some());
193        assert_eq!(
194            time_tracker.state.clone().unwrap(),
195            State::Focus(Some(focus_time))
196        );
197
198        // Focus -> Break
199        let to_spend_opt = time_tracker.switch(focus_time);
200        assert!(time_tracker.state.is_some());
201        assert_eq!(
202            time_tracker.state.clone().unwrap(),
203            State::Break(Some(break_time))
204        );
205        assert_eq!(State::Break(Some(break_time)), to_spend_opt);
206
207        // Break skipped early -> Focus
208        let break_time_skipped = Duration::from_secs(3);
209        time_tracker.switch(break_time - break_time_skipped);
210
211        // Focus skipped early -> Break
212        let focus_time_skipped = Duration::from_secs(9);
213        let to_spend_opt = time_tracker.switch(focus_time - focus_time_skipped);
214
215        // Is break time extended ?
216        assert!(time_tracker.state.is_some());
217        assert_eq!(
218            time_tracker.state.clone().unwrap(),
219            State::Break(Some(break_time + break_time_skipped))
220        );
221        assert_eq!(
222            State::Break(Some(break_time + break_time_skipped)),
223            to_spend_opt
224        );
225
226        // Break -> Focus
227        let to_spend_opt = time_tracker.switch(break_time + break_time_skipped);
228
229        // Is focus time extended ?
230        assert_eq!(
231            State::Focus(Some(focus_time + focus_time_skipped)),
232            to_spend_opt
233        );
234        assert!(time_tracker.state.is_some());
235        assert_eq!(
236            time_tracker.state.clone().unwrap(),
237            State::Focus(Some(focus_time + focus_time_skipped))
238        );
239
240        // Break time is normal
241        let to_spend_opt = time_tracker.switch(focus_time + focus_time_skipped);
242        assert!(time_tracker.state.is_some());
243        assert_eq!(
244            time_tracker.state.clone().unwrap(),
245            State::Break(Some(break_time))
246        );
247        assert_eq!(State::Break(Some(break_time)), to_spend_opt);
248
249        // Focus time is normal
250        let to_spend_opt = time_tracker.switch(break_time);
251        assert!(time_tracker.state.is_some());
252        assert_eq!(
253            time_tracker.state.clone().unwrap(),
254            State::Focus(Some(focus_time))
255        );
256        assert_eq!(State::Focus(Some(focus_time)), to_spend_opt);
257    }
258}