feat: add sound notification #6

Merged
Stevan merged 8 commits from stevanfreeborn/feat/add-sound-notification into main 2026-07-28 22:39:01 +00:00
2 changed files with 39 additions and 53 deletions
Showing only changes of commit 71eb940afc - Show all commits
+38 -50
View File
@@ -1,3 +1,4 @@
use crate::args::CliConfig;
use crate::constants::SECONDS_PER_MIN; use crate::constants::SECONDS_PER_MIN;
use crate::msg::Message; use crate::msg::Message;
use crate::notification::send_desktop_notification; use crate::notification::send_desktop_notification;
@@ -10,30 +11,25 @@ pub enum TimerMode {
} }
pub struct Termato { pub struct Termato {
pub work_mins: u32, pub config: CliConfig,
pub break_mins: u32,
pub mode: TimerMode, pub mode: TimerMode,
pub is_running: bool, pub is_running: bool,
pub duration_in_secs: u32, pub duration_in_secs: u32,
pub time_remaining_in_sec: u32, pub time_remaining_in_sec: u32,
pub show_help: bool, pub show_help: bool,
pub enable_notifications: bool,
pub enable_sound: bool,
should_quit: bool, should_quit: bool,
} }
impl Termato { impl Termato {
pub fn new(work_mins: u32, break_mins: u32) -> Self { pub fn new(config: CliConfig) -> Self {
let duration_in_secs = config.work_mins * SECONDS_PER_MIN;
Termato { Termato {
work_mins, config,
break_mins,
mode: TimerMode::Work, mode: TimerMode::Work,
is_running: false, is_running: false,
duration_in_secs: work_mins * SECONDS_PER_MIN, duration_in_secs,
time_remaining_in_sec: work_mins * SECONDS_PER_MIN, time_remaining_in_sec: duration_in_secs,
show_help: false, show_help: false,
enable_notifications: false,
enable_sound: false,
should_quit: false, should_quit: false,
} }
} }
@@ -42,16 +38,6 @@ impl Termato {
self.should_quit self.should_quit
} }
pub fn with_notifications(mut self, enable: bool) -> Self {
self.enable_notifications = enable;
self
}
pub fn with_sound(mut self, enable: bool) -> Self {
self.enable_sound = enable;
self
}
pub fn update(&mut self, msg: Message) { pub fn update(&mut self, msg: Message) {
match msg { match msg {
Message::Tick => self.tick(), Message::Tick => self.tick(),
@@ -73,8 +59,8 @@ impl Termato {
TimerMode::Break => TimerMode::Work, TimerMode::Break => TimerMode::Work,
}; };
self.duration_in_secs = match self.mode { self.duration_in_secs = match self.mode {
TimerMode::Work => self.work_mins * SECONDS_PER_MIN, TimerMode::Work => self.config.work_mins * SECONDS_PER_MIN,
TimerMode::Break => self.break_mins * SECONDS_PER_MIN, TimerMode::Break => self.config.break_mins * SECONDS_PER_MIN,
}; };
self.time_remaining_in_sec = self.duration_in_secs; self.time_remaining_in_sec = self.duration_in_secs;
} }
@@ -84,14 +70,14 @@ impl Termato {
self.time_remaining_in_sec -= 1; self.time_remaining_in_sec -= 1;
if self.time_remaining_in_sec == 0 { if self.time_remaining_in_sec == 0 {
if self.enable_notifications { if self.config.enable_notifications {
match self.mode { match self.mode {
TimerMode::Work => send_desktop_notification("Work Done", "Time to take a break."), TimerMode::Work => send_desktop_notification("Work Done", "Time to take a break."),
TimerMode::Break => send_desktop_notification("Break Over", "Time to focus."), TimerMode::Break => send_desktop_notification("Break Over", "Time to focus."),
} }
} }
if self.enable_sound { if self.config.enable_sound {
play_notification_sound(); play_notification_sound();
} }
@@ -103,7 +89,7 @@ impl Termato {
pub fn reset(&mut self) { pub fn reset(&mut self) {
self.is_running = false; self.is_running = false;
self.mode = TimerMode::Work; self.mode = TimerMode::Work;
self.duration_in_secs = self.work_mins * SECONDS_PER_MIN; self.duration_in_secs = self.config.work_mins * SECONDS_PER_MIN;
self.time_remaining_in_sec = self.duration_in_secs; self.time_remaining_in_sec = self.duration_in_secs;
} }
@@ -116,24 +102,35 @@ impl Termato {
mod tests { mod tests {
use super::*; use super::*;
fn get_test_config(work_mins: u32, break_mins: u32) -> CliConfig {
CliConfig {
work_mins,
break_mins,
enable_notifications: false,
enable_sound: false,
enable_visualizer: false,
}
}
#[test] #[test]
fn termato_new_when_called_it_should_return_expected_starting_state() { fn termato_new_when_called_it_should_return_expected_starting_state() {
let result = Termato::new(25, 5); let result = Termato::new(get_test_config(25, 5));
assert_eq!(result.work_mins, 25); assert_eq!(result.config.work_mins, 25);
assert_eq!(result.break_mins, 5); assert_eq!(result.config.break_mins, 5);
assert_eq!(result.mode, TimerMode::Work); assert_eq!(result.mode, TimerMode::Work);
assert!(!result.is_running); assert!(!result.is_running);
assert_eq!(result.duration_in_secs, 1500); assert_eq!(result.duration_in_secs, 1500);
assert_eq!(result.time_remaining_in_sec, 1500); assert_eq!(result.time_remaining_in_sec, 1500);
assert!(!result.show_help); assert!(!result.show_help);
assert!(!result.enable_notifications); assert!(!result.config.enable_notifications);
assert!(!result.config.enable_sound);
assert!(!result.should_quit); assert!(!result.should_quit);
} }
#[test] #[test]
fn termato_update_when_called_with_messages_it_should_update_state() { fn termato_update_when_called_with_messages_it_should_update_state() {
let mut termato = Termato::new(25, 5); let mut termato = Termato::new(get_test_config(25, 5));
termato.update(Message::ToggleRunning); termato.update(Message::ToggleRunning);
assert!(termato.is_running); assert!(termato.is_running);
@@ -154,7 +151,7 @@ mod tests {
#[test] #[test]
fn termato_tick_when_called_and_not_running_it_should_do_nothing() { fn termato_tick_when_called_and_not_running_it_should_do_nothing() {
let mut termato = Termato::new(1, 1); let mut termato = Termato::new(get_test_config(1, 1));
termato.update(Message::Tick); termato.update(Message::Tick);
@@ -163,7 +160,7 @@ mod tests {
#[test] #[test]
fn termato_tick_when_called_and_no_time_remaining_it_should_do_nothing() { fn termato_tick_when_called_and_no_time_remaining_it_should_do_nothing() {
let mut termato = Termato::new(1, 1); let mut termato = Termato::new(get_test_config(1, 1));
termato.is_running = true; termato.is_running = true;
termato.time_remaining_in_sec = 0; termato.time_remaining_in_sec = 0;
@@ -174,7 +171,7 @@ mod tests {
#[test] #[test]
fn termato_tick_when_running_and_time_reamining_it_should_reduce_remaining_by_one() { fn termato_tick_when_running_and_time_reamining_it_should_reduce_remaining_by_one() {
let mut termato = Termato::new(1, 1); let mut termato = Termato::new(get_test_config(1, 1));
termato.is_running = true; termato.is_running = true;
termato.update(Message::Tick); termato.update(Message::Tick);
@@ -184,7 +181,7 @@ mod tests {
#[test] #[test]
fn termato_tick_when_running_on_work_and_time_remaining_reaches_zero_it_should_toggle_to_break() { fn termato_tick_when_running_on_work_and_time_remaining_reaches_zero_it_should_toggle_to_break() {
let mut termato = Termato::new(1, 2); let mut termato = Termato::new(get_test_config(1, 2));
termato.is_running = true; termato.is_running = true;
termato.time_remaining_in_sec = 1; termato.time_remaining_in_sec = 1;
@@ -197,7 +194,7 @@ mod tests {
#[test] #[test]
fn termato_tick_when_running_on_break_and_time_remaining_reaches_zero_it_should_toggle_to_work() { fn termato_tick_when_running_on_break_and_time_remaining_reaches_zero_it_should_toggle_to_work() {
let mut termato = Termato::new(2, 1); let mut termato = Termato::new(get_test_config(2, 1));
termato.mode = TimerMode::Break; termato.mode = TimerMode::Break;
termato.is_running = true; termato.is_running = true;
termato.time_remaining_in_sec = 1; termato.time_remaining_in_sec = 1;
@@ -211,7 +208,7 @@ mod tests {
#[test] #[test]
fn termato_toggle_mode_when_called_it_should_toggle_the_mode() { fn termato_toggle_mode_when_called_it_should_toggle_the_mode() {
let mut termato = Termato::new(2, 1); let mut termato = Termato::new(get_test_config(2, 1));
termato.toggle_mode(); termato.toggle_mode();
assert_eq!(termato.mode, TimerMode::Break); assert_eq!(termato.mode, TimerMode::Break);
@@ -226,7 +223,7 @@ mod tests {
#[test] #[test]
fn termato_toggle_running_when_called_and_already_running_it_should_pause() { fn termato_toggle_running_when_called_and_already_running_it_should_pause() {
let mut termato = Termato::new(1, 1); let mut termato = Termato::new(get_test_config(1, 1));
termato.is_running = true; termato.is_running = true;
termato.toggle_running(); termato.toggle_running();
@@ -236,7 +233,7 @@ mod tests {
#[test] #[test]
fn termato_toggle_running_when_called_and_already_paused_it_should_run() { fn termato_toggle_running_when_called_and_already_paused_it_should_run() {
let mut termato = Termato::new(1, 1); let mut termato = Termato::new(get_test_config(1, 1));
termato.toggle_running(); termato.toggle_running();
@@ -245,7 +242,7 @@ mod tests {
#[test] #[test]
fn termato_rest_when_called_it_should_reset_the_apps_state() { fn termato_rest_when_called_it_should_reset_the_apps_state() {
let mut termato = Termato::new(1, 1); let mut termato = Termato::new(get_test_config(1, 1));
termato.is_running = true; termato.is_running = true;
termato.mode = TimerMode::Break; termato.mode = TimerMode::Break;
termato.duration_in_secs = 1; termato.duration_in_secs = 1;
@@ -261,7 +258,7 @@ mod tests {
#[test] #[test]
fn termato_toggle_help_when_called_it_should_toggle_help() { fn termato_toggle_help_when_called_it_should_toggle_help() {
let mut termato = Termato::new(1, 1); let mut termato = Termato::new(get_test_config(1, 1));
termato.toggle_help(); termato.toggle_help();
@@ -271,13 +268,4 @@ mod tests {
assert!(!termato.show_help); assert!(!termato.show_help);
} }
#[test]
fn termato_notifications_when_called_it_should_verify_opt_in_behavior() {
let result = Termato::new(25, 5);
assert!(!result.enable_notifications);
let result = result.with_notifications(true);
assert!(result.enable_notifications);
}
} }
+1 -3
View File
@@ -61,9 +61,7 @@ fn main() -> Result<(), io::Error> {
None None
}; };
let mut termato = Termato::new(config.work_mins, config.break_mins) let mut termato = Termato::new(config);
.with_notifications(config.enable_notifications)
.with_sound(config.enable_sound);
let (tx, rx) = mpsc::channel(); let (tx, rx) = mpsc::channel();