domasi/src/pomodoro.rs

234 lines
6.2 KiB
Rust
Raw Normal View History

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<P: AsRef<Path>>(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<P: AsRef<Path>>(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<Status>,
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<Status>, 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)
}
}