@@ -20,11 +20,14 @@ use ratatui::{
2020use crate :: pomodoro:: { Mode , Pomodoro } ;
2121use crate :: radio:: Radio ;
2222use crate :: theme:: Theme ;
23+ use crate :: todo:: TodoList ;
2324use crate :: ui:: logo:: { self , LOGO_HEIGHT , LOGO_WIDTH } ;
2425
2526mod pomodoro;
2627mod radio;
28+ mod storage;
2729mod theme;
30+ mod todo;
2831mod ui;
2932
3033pub fn run ( ) -> Result < ( ) > {
@@ -51,6 +54,7 @@ fn run_app(terminal: &mut Terminal<CrosstermBackend<Stdout>>) -> Result<()> {
5154 let mut frame: u64 = 0 ;
5255 let mut pomo = Pomodoro :: new ( ) ;
5356 let mut radio = Radio :: new ( ) ;
57+ let mut todos = TodoList :: load ( ) ;
5458 let mut last_second = Instant :: now ( ) ;
5559
5660 loop {
@@ -122,7 +126,11 @@ fn run_app(terminal: &mut Terminal<CrosstermBackend<Stdout>>) -> Result<()> {
122126 } ;
123127
124128 let pomo_action = if pomo. running { "Pause" } else { "Continue" } ;
125- let radio_action = if radio. is_playing ( ) || radio. is_loading ( ) { "stop" } else { "play" } ;
129+ let radio_action = if radio. is_playing ( ) || radio. is_loading ( ) {
130+ "stop"
131+ } else {
132+ "play"
133+ } ;
126134
127135 let help = Paragraph :: new ( Line :: from ( vec ! [
128136 Span :: styled( "q " , Theme :: accent( ) ) ,
@@ -131,18 +139,27 @@ fn run_app(terminal: &mut Terminal<CrosstermBackend<Stdout>>) -> Result<()> {
131139 Span :: styled( format!( "{} " , radio_action) , Theme :: frame( ) ) ,
132140 Span :: styled( "←/→ " , Theme :: accent( ) ) ,
133141 Span :: styled( "station " , Theme :: frame( ) ) ,
142+ Span :: styled( "t " , Theme :: accent( ) ) ,
143+ Span :: styled( "todos " , Theme :: frame( ) ) ,
134144 Span :: styled( "p " , Theme :: accent( ) ) ,
135145 Span :: styled( "pomo " , Theme :: frame( ) ) ,
136146 Span :: styled( "space " , Theme :: accent( ) ) ,
137- Span :: styled( format!( "{} " , pomo_action) , Theme :: frame( ) ) ,
138- Span :: styled( "r " , Theme :: accent( ) ) ,
139- Span :: styled( "reset" , Theme :: frame( ) ) ,
147+ Span :: styled( pomo_action, Theme :: frame( ) ) ,
140148 ] ) )
141149 . style ( Theme :: base ( ) ) ;
142150
151+ // Todo list in center area
152+ let todo_area = Rect {
153+ x : area. x + 4 ,
154+ y : area. y + 3 ,
155+ width : area. width . saturating_sub ( 8 ) ,
156+ height : area. height . saturating_sub ( 8 ) ,
157+ } ;
158+
143159 f. render_widget ( logo:: logo ( ) , logo_area) ;
144160 f. render_widget ( station_line, station_area) ;
145161 f. render_widget ( help, help_area) ;
162+ todos. draw ( f, todo_area) ;
146163
147164 // Pomodoro on top
148165 if pomo. visible {
@@ -152,7 +169,11 @@ fn run_app(terminal: &mut Terminal<CrosstermBackend<Stdout>>) -> Result<()> {
152169 Mode :: Break => "BREAK" ,
153170 } ;
154171 let status = if pomo. running { "▶" } else { "⏸" } ;
155- let timer_style = if pomo. running { Theme :: accent ( ) } else { Theme :: frame ( ) } ;
172+ let timer_style = if pomo. running {
173+ Theme :: accent ( )
174+ } else {
175+ Theme :: frame ( )
176+ } ;
156177
157178 let pomo_widget = Paragraph :: new ( vec ! [
158179 Line :: from( Span :: styled( format!( "{:02}:{:02}" , mm, ss) , Theme :: hot( ) ) ) ,
@@ -176,19 +197,63 @@ fn run_app(terminal: &mut Terminal<CrosstermBackend<Stdout>>) -> Result<()> {
176197 // input
177198 if event:: poll ( timeout) ? {
178199 if let Event :: Key ( key) = event:: read ( ) ? {
179- match key. code {
180- KeyCode :: Char ( 'q' ) => {
181- radio. stop ( ) ;
182- return Ok ( ( ) ) ;
200+ // Todo input mode captures all keys
201+ if todos. input_mode {
202+ match key. code {
203+ KeyCode :: Enter => todos. confirm_input ( ) ,
204+ KeyCode :: Esc => todos. cancel_input ( ) ,
205+ KeyCode :: Backspace => todos. backspace ( ) ,
206+ KeyCode :: Char ( c) => todos. type_char ( c) ,
207+ _ => { }
208+ }
209+ } else if todos. visible {
210+ // Todo visible - handle todo keys first
211+ match key. code {
212+ KeyCode :: Char ( 'q' ) => {
213+ todos. save ( ) ;
214+ radio. stop ( ) ;
215+ return Ok ( ( ) ) ;
216+ }
217+ KeyCode :: Char ( 't' ) => todos. toggle_visible ( ) ,
218+ KeyCode :: Char ( 'n' ) => todos. enter_input_mode ( ) ,
219+ KeyCode :: Char ( 'j' ) => todos. move_down ( ) ,
220+ KeyCode :: Char ( 'k' ) => todos. move_up ( ) ,
221+ KeyCode :: Char ( 'x' ) => todos. toggle_completed ( ) ,
222+ KeyCode :: Char ( 'd' ) => todos. delete_selected ( ) ,
223+ KeyCode :: Enter => todos. select_for_pomodoro ( ) ,
224+ KeyCode :: Char ( 's' ) => radio. toggle ( ) ,
225+ KeyCode :: Left => radio. prev_station ( ) ,
226+ KeyCode :: Right => radio. next_station ( ) ,
227+ KeyCode :: Char ( ' ' ) => {
228+ // Auto-track selected task if none tracked
229+ if todos. active_task . is_none ( ) && !todos. tasks . is_empty ( ) {
230+ todos. select_for_pomodoro ( ) ;
231+ }
232+ pomo. start_pause ( ) ;
233+ }
234+ KeyCode :: Char ( 'r' ) => pomo. stop_reset ( ) ,
235+ KeyCode :: Char ( '+' ) => pomo. add_five_minutes ( ) ,
236+ KeyCode :: Esc => todos. toggle_visible ( ) ,
237+ _ => { }
238+ }
239+ } else {
240+ // Normal mode
241+ match key. code {
242+ KeyCode :: Char ( 'q' ) => {
243+ todos. save ( ) ;
244+ radio. stop ( ) ;
245+ return Ok ( ( ) ) ;
246+ }
247+ KeyCode :: Char ( 't' ) => todos. toggle_visible ( ) ,
248+ KeyCode :: Char ( 'p' ) => pomo. toggle_visible ( ) ,
249+ KeyCode :: Char ( 's' ) => radio. toggle ( ) ,
250+ KeyCode :: Left => radio. prev_station ( ) ,
251+ KeyCode :: Right => radio. next_station ( ) ,
252+ KeyCode :: Char ( ' ' ) => pomo. start_pause ( ) ,
253+ KeyCode :: Char ( 'r' ) => pomo. stop_reset ( ) ,
254+ KeyCode :: Char ( '+' ) => pomo. add_five_minutes ( ) ,
255+ _ => { }
183256 }
184- KeyCode :: Char ( 'p' ) => pomo. toggle_visible ( ) ,
185- KeyCode :: Char ( 's' ) => radio. toggle ( ) ,
186- KeyCode :: Left => radio. prev_station ( ) ,
187- KeyCode :: Right => radio. next_station ( ) ,
188- KeyCode :: Char ( ' ' ) => pomo. start_pause ( ) ,
189- KeyCode :: Char ( 'r' ) => pomo. stop_reset ( ) ,
190- KeyCode :: Char ( '+' ) => pomo. add_five_minutes ( ) ,
191- _ => { }
192257 }
193258 }
194259 }
@@ -208,6 +273,14 @@ fn run_app(terminal: &mut Terminal<CrosstermBackend<Stdout>>) -> Result<()> {
208273 radio. set_volume ( vol) ;
209274 }
210275
276+ // Track time on active task
277+ if pomo. running {
278+ if let Some ( task_id) = todos. active_task {
279+ todos. add_time ( task_id, Duration :: from_secs ( 1 ) ) ;
280+ todos. save_throttled ( ) ;
281+ }
282+ }
283+
211284 if pomo. tick_1s ( ) {
212285 pomo. play_notification ( ) ;
213286 // Restore volume after a delay (notification lasts ~1.5s)
0 commit comments