feat: implement the ability to skip
This commit is contained in:
parent
d94926a235
commit
325304deff
90
src/alert.rs
90
src/alert.rs
|
@ -1,8 +1,9 @@
|
||||||
use rodio::{Decoder, OutputStream, Sink};
|
use rodio::{decoder::DecoderError, Decoder, OutputStream, PlayError, Sink, StreamError};
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::BufReader;
|
use std::io::BufReader;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
use std::{error::Error, fmt::Display, fmt::Formatter, fmt::Result as FmtResult};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Alert {
|
pub struct Alert {
|
||||||
|
@ -18,15 +19,38 @@ impl Alert {
|
||||||
self.path = path;
|
self.path = path;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn play(&self) {
|
// pub fn play(&self) -> Result<(), Box<dyn Error>> {
|
||||||
|
// let file = File::open(&self.path).unwrap();
|
||||||
|
|
||||||
|
// thread::Builder::new()
|
||||||
|
// .name("Audio Thread".to_string())
|
||||||
|
// .spawn(|| -> Result<(), Error> {
|
||||||
|
// let (_stream, handle) = OutputStream::try_default()?;
|
||||||
|
// let source = Decoder::new(BufReader::new(file))?;
|
||||||
|
// let sink = Sink::try_new(&handle)?;
|
||||||
|
|
||||||
|
// sink.append(source);
|
||||||
|
|
||||||
|
// loop {
|
||||||
|
// if sink.len() == 0 {
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Ok(())
|
||||||
|
// })?;
|
||||||
|
// Ok(())
|
||||||
|
// }
|
||||||
|
|
||||||
|
pub fn play(&self) -> Result<(), Box<dyn Error>> {
|
||||||
let file = File::open(&self.path).unwrap();
|
let file = File::open(&self.path).unwrap();
|
||||||
|
|
||||||
thread::Builder::new()
|
thread::Builder::new()
|
||||||
.name("Audio Thread".to_string())
|
.name("Alert Playback Thread".to_string())
|
||||||
.spawn(move || {
|
.spawn(move || -> Result<(), PlaybackError> {
|
||||||
let (_stream, handle) = OutputStream::try_default().unwrap();
|
let (_stream, handle) = OutputStream::try_default()?;
|
||||||
let source = Decoder::new(BufReader::new(file)).unwrap();
|
let source = Decoder::new(BufReader::new(file))?;
|
||||||
let sink = Sink::try_new(&handle).unwrap();
|
let sink = Sink::try_new(&handle)?;
|
||||||
|
|
||||||
sink.append(source);
|
sink.append(source);
|
||||||
|
|
||||||
|
@ -35,7 +59,55 @@ impl Alert {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
|
||||||
.unwrap();
|
Ok(())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum PlaybackError {
|
||||||
|
StreamError(StreamError),
|
||||||
|
DecoderError(DecoderError),
|
||||||
|
SinkError(PlayError),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<DecoderError> for PlaybackError {
|
||||||
|
fn from(err: DecoderError) -> Self {
|
||||||
|
Self::DecoderError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<StreamError> for PlaybackError {
|
||||||
|
fn from(err: StreamError) -> Self {
|
||||||
|
Self::StreamError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<PlayError> for PlaybackError {
|
||||||
|
fn from(err: PlayError) -> Self {
|
||||||
|
Self::SinkError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error for PlaybackError {
|
||||||
|
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||||
|
match &self {
|
||||||
|
Self::StreamError(err) => Some(err),
|
||||||
|
Self::DecoderError(err) => Some(err),
|
||||||
|
Self::SinkError(err) => Some(err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for PlaybackError {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
||||||
|
match &self {
|
||||||
|
Self::StreamError(err) => write!(f, "{}", err),
|
||||||
|
Self::DecoderError(err) => write!(f, "{}", err),
|
||||||
|
Self::SinkError(err) => write!(f, "{}", err),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ static SHORT_BREAK_TIME: u64 = 300; // Default: 300 (5min)
|
||||||
static LONG_BREAK_TIME: u64 = 600; // Default: 600 (10min)
|
static LONG_BREAK_TIME: u64 = 600; // Default: 600 (10min)
|
||||||
static POLLING_RATE: Duration = Duration::from_millis(100);
|
static POLLING_RATE: Duration = Duration::from_millis(100);
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug)]
|
||||||
pub struct Domasi {
|
pub struct Domasi {
|
||||||
state: State,
|
state: State,
|
||||||
count: u64,
|
count: u64,
|
||||||
|
@ -32,7 +32,7 @@ impl Default for Domasi {
|
||||||
|
|
||||||
impl Domasi {
|
impl Domasi {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self::default()
|
Default::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start(&mut self, maybe_alert: Option<Alert>) -> crossterm::Result<()> {
|
pub fn start(&mut self, maybe_alert: Option<Alert>) -> crossterm::Result<()> {
|
||||||
|
@ -67,18 +67,11 @@ impl Domasi {
|
||||||
self.paused.toggle();
|
self.paused.toggle();
|
||||||
|
|
||||||
if !self.paused.value {
|
if !self.paused.value {
|
||||||
// Apply the elapsed time
|
self.apply_paused_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();
|
self.paused = Default::default();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
KeyCode::Char('s') | KeyCode::Char('S') => self.skip(maybe_alert.as_ref()),
|
||||||
KeyCode::Char('r') | KeyCode::Char('R') => self.restart(),
|
KeyCode::Char('r') | KeyCode::Char('R') => self.restart(),
|
||||||
KeyCode::Char('q') | KeyCode::Char('Q') => break,
|
KeyCode::Char('q') | KeyCode::Char('Q') => break,
|
||||||
KeyCode::Char('c') if key_event.modifiers == KeyModifiers::CONTROL => break,
|
KeyCode::Char('c') if key_event.modifiers == KeyModifiers::CONTROL => break,
|
||||||
|
@ -92,6 +85,44 @@ impl Domasi {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn apply_paused_time(&mut self) {
|
||||||
|
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."),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn skip(&mut self, maybe_alert: Option<&Alert>) {
|
||||||
|
if self.paused.value {
|
||||||
|
self.paused = Default::default();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.next();
|
||||||
|
|
||||||
|
if let Some(alert) = maybe_alert {
|
||||||
|
let _ = alert.play();
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = self.notify();
|
||||||
|
self.display_time()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next(&mut self) {
|
||||||
|
let (do_update_count, next_state) = self.get_next();
|
||||||
|
|
||||||
|
if let Count::Increase = do_update_count {
|
||||||
|
self.count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.wait_start = match next_state {
|
||||||
|
State::UserWait => None,
|
||||||
|
State::Work | State::ShortBreak | State::LongBreak => Some(Instant::now()),
|
||||||
|
State::Start => unreachable!("Domasi#next should never return State::Start"),
|
||||||
|
};
|
||||||
|
self.state = next_state;
|
||||||
|
}
|
||||||
|
|
||||||
fn restart(&mut self) {
|
fn restart(&mut self) {
|
||||||
self.wait_start = Some(Instant::now());
|
self.wait_start = Some(Instant::now());
|
||||||
self.paused = Default::default();
|
self.paused = Default::default();
|
||||||
|
@ -169,21 +200,10 @@ impl Domasi {
|
||||||
}
|
}
|
||||||
Status::Active => self.display_time(),
|
Status::Active => self.display_time(),
|
||||||
Status::NextState => {
|
Status::NextState => {
|
||||||
let (update_count, new_state) = self.next();
|
self.next();
|
||||||
|
|
||||||
if let Count::Increase = update_count {
|
|
||||||
self.count += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.state = new_state;
|
|
||||||
self.wait_start = match new_state {
|
|
||||||
State::UserWait => None,
|
|
||||||
State::Work | State::ShortBreak | State::LongBreak => Some(Instant::now()),
|
|
||||||
State::Start => unreachable!("Domasi#next should never return State::Start"),
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(alert) = maybe_alert {
|
if let Some(alert) = maybe_alert {
|
||||||
alert.play()
|
let _ = alert.play();
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = self.notify();
|
let _ = self.notify();
|
||||||
|
@ -229,7 +249,7 @@ impl Domasi {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next(&self) -> (Count, State) {
|
fn get_next(&self) -> (Count, State) {
|
||||||
match self.state {
|
match self.state {
|
||||||
State::Work => {
|
State::Work => {
|
||||||
let state: State = if (self.count + 1) % 4 == 0 {
|
let state: State = if (self.count + 1) % 4 == 0 {
|
||||||
|
|
Loading…
Reference in New Issue