use super::Config; use rodio::{Decoder, Device, Source}; use std::fs::File; use std::io::BufReader; use std::path::Path; use std::sync::mpsc::Sender; use std::time::{Duration, Instant}; // #[derive()] pub struct Alert<'a> { path: &'a Path, device: &'a Device, } impl<'a> Alert<'a> { pub fn new>(path: &'a P, device: &'a Device) -> Alert<'a> { Alert { path: path.as_ref(), device, } } pub fn play(&self) { if self.path.exists() { let file = File::open(self.path).unwrap(); let source = Decoder::new(BufReader::new(file)).unwrap(); rodio::play_raw(&self.device, source.convert_samples()); } } pub fn load>(mut self, new_path: &'a P) -> Self { self.path = new_path.as_ref(); self } } #[derive(Copy, Clone, Debug)] pub enum State { Work, ShortBreak, LongBreak, Inactive, } impl std::fmt::Display for State { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match *self { State::Work => f.write_str("Working: "), State::ShortBreak => f.write_str("Resting: "), State::LongBreak => f.write_str("REALLY Resting: "), State::Inactive => f.write_str("Inactive: "), } } } #[derive(Copy, Clone, Debug)] pub struct Pomodoro { count: u64, state: State, } impl Default for Pomodoro { fn default() -> Self { Pomodoro { count: 0, state: State::Inactive, } } } impl<'a> Pomodoro { pub async fn start( &mut self, config: Config, tx: Sender, maybe_alert: Option<&Alert<'a>>, ) { loop { self.next(); if let Some(alert) = maybe_alert { alert.play(); } Self::notify(&self.state); match self.state { State::Work => { Self::send_to_clock(&tx, self.state, config.work_time); Self::wait(config.work_time).await; } State::ShortBreak => { Self::send_to_clock(&tx, self.state, config.short_break); Self::wait(config.short_break).await; } State::LongBreak => { Self::send_to_clock(&tx, self.state, config.long_break); Self::wait(config.long_break).await; } State::Inactive => { println!("Pomodoro Cycle is complete"); break; } } } } } impl Pomodoro { pub fn new() -> Pomodoro { Pomodoro { count: 0, state: State::Inactive, } } fn next(&mut self) { match self.state { State::Work => { self.count += 1; if (self.count % 4) == 0 { self.state = State::LongBreak; } else { self.state = State::ShortBreak; } } State::LongBreak => self.state = State::Inactive, State::ShortBreak => self.state = State::Work, State::Inactive => self.state = State::Work, } } async fn wait(duration: Duration) { async_std::task::sleep(duration).await; } fn send_to_clock(tx: &Sender, state: State, length: Duration) { let status = Status { start: Instant::now(), length, state, }; tx.send(status).unwrap(); } fn notify(state: &State) { let mut toast = notify_rust::Notification::new(); match state { State::Work => { toast .summary("Time to Work!") .body("Remember to stay focused!") .show() .unwrap(); } State::ShortBreak | State::LongBreak => { toast .summary("Break Time!") .body("Enjoy your well deserved rest!") .show() .unwrap(); } State::Inactive => { toast .summary("Pomodoro Cycle Complete.") .body("Now waiting for user input....") .show() .unwrap(); } } } } #[derive(Copy, Clone, Debug)] pub struct Status { pub start: Instant, pub length: Duration, pub state: State, } pub struct Clock; impl Clock { pub fn get_formatted_string(status: Status) -> (u64, String) { let now = Instant::now(); let elapsed = now.checked_duration_since(status.start); match elapsed { Some(duration) => { let remaining = status.length.as_secs() - duration.as_secs(); let seconds = remaining; let hours = seconds / 3600; let minutes = (seconds - (hours * 3600)) / 60; let seconds = seconds - (hours * 3600) - (minutes * 60); let mut clock = String::new(); clock.push_str(&format!("{}", status.state)); if hours > 0 { // We don't want o bother with the hours part if there is none if hours < 10 { clock.push_str(&format!("0{}:", hours)); } else { clock.push_str(&format!("{}:", hours)); } } if minutes < 10 { clock.push_str(&format!("0{}:", minutes)); } else { clock.push_str(&format!("{}:", minutes)); } if seconds < 10 { clock.push_str(&format!("0{}", seconds)); } else { clock.push_str(&format!("{}", seconds)); } (remaining, clock) } None => (0, "??:??:??".to_string()), // This will break the loop } } pub fn get_polling_interval() -> Duration { Duration::from_millis(500) } }