domasi/src/pomodoro.rs

355 lines
10 KiB
Rust
Raw Normal View History

2020-08-31 05:18:39 +00:00
use super::Alert;
2020-12-14 03:44:56 +00:00
use crossterm::event::{self, Event, KeyCode, KeyModifiers};
use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
use std::error::Error;
2020-08-24 03:27:49 +00:00
use std::fmt::{Display, Formatter, Result as FmtResult};
2020-08-31 01:22:27 +00:00
use std::io::{self, Write};
use std::time::{Duration, Instant};
2020-08-31 01:22:27 +00:00
#[derive(Debug, Copy, Clone, Default)]
pub struct Pomodoro {
state: State,
count: u64,
wait_start: Option<Instant>,
paused: PausedState,
}
static POMODORO_CYCLES: u64 = 5;
2020-08-31 05:29:04 +00:00
static WORK_TIME: u64 = 1500; // Default: 1500 (25min)
static SHORT_BREAK_TIME: u64 = 300; // Default: 300 (5min)
static LONG_BREAK_TIME: u64 = 600; // Default: 600 (10min)
2020-08-31 01:22:27 +00:00
2020-12-14 03:44:56 +00:00
static POLLING_RATE: Duration = Duration::from_millis(100);
2020-08-31 01:22:27 +00:00
impl Pomodoro {
pub fn new() -> Self {
Self::default()
}
2020-12-14 03:44:56 +00:00
pub fn start(&mut self, maybe_alert: Option<Alert>) -> crossterm::Result<()> {
enable_raw_mode()?;
2020-08-31 05:18:39 +00:00
2020-08-31 01:22:27 +00:00
loop {
2020-12-14 03:44:56 +00:00
if let Status::Complete = self.poll(maybe_alert.as_ref()) {
2020-08-31 01:22:27 +00:00
break;
}
2020-08-31 05:18:39 +00:00
2020-12-14 03:44:56 +00:00
if event::poll(POLLING_RATE)? {
if let Event::Key(key_event) = event::read()? {
match key_event.code {
KeyCode::Char('p') | KeyCode::Char('P') => {
self.paused.toggle();
if !self.paused.value {
// Apply the elapsed time
let duration = self.paused.duration;
match self.wait_start {
Some(instant) => self.wait_start = Some(instant + duration),
None => {
unreachable!("Unable to resume from a non-existent timer.")
}
}
self.paused = Default::default();
}
}
KeyCode::Char('r') | KeyCode::Char('R') => self.restart(),
2020-12-14 03:44:56 +00:00
KeyCode::Char('q') | KeyCode::Char('Q') => break,
KeyCode::Char('c') if key_event.modifiers == KeyModifiers::CONTROL => break,
_ => {}
}
}
}
}
2020-12-14 03:44:56 +00:00
disable_raw_mode()?;
Ok(())
2020-08-31 05:18:39 +00:00
}
fn restart(&mut self) {
self.wait_start = Some(Instant::now());
self.paused = Default::default();
}
fn notify_complete(&self) -> Result<(), Box<dyn Error>> {
let mut toast = notify_rust::Notification::new();
toast.summary("Pomodoro cycle complete!");
toast.body("domasi will now exit.");
toast.show()?;
Ok(())
}
fn notify(&self) -> Result<(), Box<dyn Error>> {
fn cycle_count(i: u64) -> String {
let i = i / 4;
match i {
1 => "After this, you will have completed 1 cycle.".to_string(),
_ => format!("After this, you will have completed {} cycles.", i),
}
}
fn session_num(i: u64) -> &'static str {
match i % 4 {
0 => "Time for your 1st work session.",
1 => "Time for your 2nd work session.",
2 => "Time for your 3rd work session.",
3 => "Time for your 4th work session.",
_ => unreachable!(),
}
}
let mut toast = notify_rust::Notification::new();
match self.state {
State::Inactive => {}
State::Work => {
toast
.summary(session_num(self.count))
.body("Remember to stay focused.")
.show()?;
}
State::ShortBreak => {
toast
.summary("Time for a quick break.")
.body("Sit back and relax.")
.show()?;
}
State::LongBreak => {
toast
.summary("Enjoy your long break!")
.body(&cycle_count(self.count))
.show()?;
}
};
Ok(())
}
2020-08-31 05:18:39 +00:00
fn poll(&mut self, maybe_alert: Option<&Alert>) -> Status {
2020-08-31 01:22:27 +00:00
let status = self.check();
2020-08-24 03:27:49 +00:00
2020-08-31 01:22:27 +00:00
match status {
Status::Paused => {
assert!(self.paused.value);
let now = Instant::now();
2020-08-31 01:22:27 +00:00
self.paused.duration += match self.paused.previous {
Some(earlier) => now - earlier,
None => Default::default(),
};
2020-08-31 01:22:27 +00:00
self.paused.previous = Some(now);
2020-12-14 03:44:56 +00:00
self.display_paused();
2020-08-31 01:22:27 +00:00
}
Status::Active => self.display_time(),
Status::NextState => {
let (update_count, new_state) = self.next();
2020-08-31 01:22:27 +00:00
if let Count::Increase = update_count {
self.count += 1;
}
2020-08-31 01:22:27 +00:00
self.state = new_state;
self.wait_start = match new_state {
State::Inactive => None,
State::Work | State::ShortBreak | State::LongBreak => Some(Instant::now()),
};
2020-08-31 05:18:39 +00:00
if let Some(alert) = maybe_alert {
alert.play()
2020-08-31 05:18:39 +00:00
}
let _ = self.notify();
2020-08-31 01:22:27 +00:00
self.display_time();
}
Status::Complete => {
println!("\rPomodoro cycle complete!");
let _ = self.notify_complete();
}
2020-08-31 01:22:27 +00:00
Status::Inactive => {}
};
2020-08-31 01:22:27 +00:00
status
}
2020-08-31 01:22:27 +00:00
fn display_time(&self) {
2020-12-14 03:44:56 +00:00
if let Some(start) = self.wait_start {
let remainder: Clock = (Self::wait_times(self.state) - (Instant::now() - start)).into();
let _ = Self::print(&format!("\r{} {}", self.state, remainder));
}
}
fn display_paused(&self) {
if let Some(start) = self.wait_start {
let stop_time: Clock = (Self::wait_times(self.state)
- ((Instant::now() - self.paused.duration) - start))
.into();
let _ = Self::print(&format!("\r[PAUSED] {} {}", self.state, stop_time));
}
}
2020-08-31 01:22:27 +00:00
fn print(text: &str) -> io::Result<()> {
let out = io::stdout();
let mut handle = out.lock();
// Empty String so that we can clear line before writing
// from the most-left side of the terminal again.
2020-12-14 03:44:56 +00:00
//
// We write 24 spaces so that the entire line is always
// written over
handle.write_all(b"\r ")?;
2020-08-31 01:22:27 +00:00
handle.write_all(text.as_bytes())?;
handle.flush()?;
Ok(())
}
2020-08-31 01:22:27 +00:00
fn next(&self) -> (Count, State) {
match self.state {
State::Work => {
2020-08-31 01:22:27 +00:00
let state: State = if (self.count + 1) % 4 == 0 {
State::LongBreak
} else {
2020-08-31 01:22:27 +00:00
State::ShortBreak
};
(Count::Increase, state)
}
2020-08-31 01:22:27 +00:00
State::ShortBreak | State::Inactive => (Count::Remain, State::Work),
State::LongBreak => (Count::Remain, State::Inactive),
}
}
2020-08-31 01:22:27 +00:00
fn check(&self) -> Status {
if self.paused.value {
return Status::Paused;
}
2020-08-31 01:22:27 +00:00
match self.wait_start {
Some(earlier) => {
let diff: Duration = (Instant::now() - self.paused.duration) - earlier;
2020-08-31 01:22:27 +00:00
if diff > Self::wait_times(self.state) {
Status::NextState
} else {
Status::Active
}
}
None => {
if self.count / 4 == POMODORO_CYCLES {
2020-08-31 01:22:27 +00:00
Status::Complete
} else {
Status::NextState
2020-08-31 01:22:27 +00:00
}
}
}
}
2020-08-31 01:22:27 +00:00
fn wait_times(state: State) -> Duration {
match state {
2020-08-31 01:22:27 +00:00
State::Work => Duration::from_secs(WORK_TIME),
State::ShortBreak => Duration::from_secs(SHORT_BREAK_TIME),
State::LongBreak => Duration::from_secs(LONG_BREAK_TIME),
2020-08-31 01:22:27 +00:00
_ => unreachable!("Can not have Pomodoro state = State::Inactive and wait_start = Some(...) at the same time.")
}
}
}
2020-08-31 01:22:27 +00:00
struct Clock {
2020-08-24 03:27:49 +00:00
hours: u64,
minutes: u64,
2020-08-31 01:22:27 +00:00
seconds: u64,
2020-08-24 03:27:49 +00:00
}
2020-08-31 01:22:27 +00:00
impl From<Duration> for Clock {
fn from(dur: Duration) -> Self {
let dur = dur.as_secs();
2020-08-31 01:22:27 +00:00
let hours = dur / 3600;
let minutes = (dur - (hours * 3600)) / 60;
let seconds = dur - (hours * 3600) - (minutes * 60);
2020-08-31 01:22:27 +00:00
Self {
hours,
minutes,
seconds,
}
2020-08-24 03:27:49 +00:00
}
}
2020-08-31 01:22:27 +00:00
impl Display for Clock {
2020-08-24 03:27:49 +00:00
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
if self.hours > 0 {
// No need to print the hours if there are none.
if self.hours < 10 {
write!(f, "0{}:", self.hours)?;
} else {
write!(f, "{}:", self.hours)?;
}
}
2020-08-24 03:27:49 +00:00
if self.minutes < 10 {
write!(f, "0{}:", self.minutes)?;
} else {
write!(f, "{}:", self.minutes)?;
}
if self.seconds < 10 {
write!(f, "0{}", self.seconds)
} else {
write!(f, "{}", self.seconds)
}
}
}
2020-08-31 01:22:27 +00:00
#[derive(Debug, Copy, Clone, Default)]
struct PausedState {
value: bool,
duration: Duration,
previous: Option<Instant>,
}
2020-12-14 03:44:56 +00:00
impl PausedState {
pub fn toggle(&mut self) {
self.value = !self.value;
}
}
2020-08-31 01:22:27 +00:00
#[derive(Debug, Copy, Clone)]
enum Count {
Increase,
Remain,
}
#[derive(Debug, Copy, Clone)]
enum Status {
Paused,
Active,
NextState,
Inactive,
Complete,
}
#[derive(Debug, Copy, Clone)]
enum State {
Inactive,
Work,
ShortBreak,
LongBreak,
}
impl Default for State {
fn default() -> Self {
State::Inactive
}
}
impl Display for State {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
match *self {
2020-12-14 03:44:56 +00:00
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:"),
2020-08-31 01:22:27 +00:00
}
}
}