From 886deb331160adfa25a2f81ce666eefa27d05439 Mon Sep 17 00:00:00 2001 From: Jens Krause <47693+sectore@users.noreply.github.com> Date: Wed, 5 Feb 2025 13:35:24 +0100 Subject: [PATCH] fix(notification): remove `callbacks` in favour of `mpsc` messaging (#64) --- src/app.rs | 38 +++++++++++++++++--------------- src/common.rs | 6 +++++ src/events.rs | 3 ++- src/widgets/clock.rs | 46 ++++++++++++++++++++------------------- src/widgets/clock_test.rs | 31 ++++++++++++-------------- src/widgets/countdown.rs | 16 +------------- src/widgets/pomodoro.rs | 24 ++------------------ 7 files changed, 70 insertions(+), 94 deletions(-) diff --git a/src/app.rs b/src/app.rs index 5a84234..29a5d2e 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,6 +1,6 @@ use crate::{ args::Args, - common::{AppEditMode, AppTime, AppTimeFormat, Content, Notification, Style}, + common::{AppEditMode, AppTime, AppTimeFormat, ClockTypeId, Content, Notification, Style}, constants::TICK_VALUE_MS, events::{self, TuiEventHandler}, storage::AppStorage, @@ -155,7 +155,6 @@ impl App { elapsed_value: elapsed_value_countdown, app_time, with_decis, - with_notification: notification == Notification::On, app_tx: app_tx.clone(), }), timer: TimerState::new( @@ -164,20 +163,9 @@ impl App { current_value: current_value_timer, tick_value: Duration::from_millis(TICK_VALUE_MS), with_decis, - app_tx: None, + app_tx: Some(app_tx.clone()), }) - .with_on_done_by_condition( - notification == Notification::On, - || { - debug!("on_done TIMER"); - let result = notify_rust::Notification::new() - .summary(&"Timer stopped by reaching its maximum value".to_uppercase()) - .show(); - if let Err(err) = result { - error!("on_done TIMER error: {err}"); - } - }, - ), + .with_name("Timer".to_owned()), ), pomodoro: PomodoroState::new(PomodoroStateArgs { mode: pomodoro_mode, @@ -186,7 +174,6 @@ impl App { initial_value_pause, current_value_pause, with_decis, - with_notification: notification == Notification::On, app_tx: app_tx.clone(), }), footer: FooterState::new(show_menu, app_time_format), @@ -253,8 +240,25 @@ impl App { // Closure to handle `AppEvent`'s let handle_app_events = |app: &mut Self, event: events::AppEvent| -> Result<()> { match event { - events::AppEvent::ClockDone => { + events::AppEvent::ClockDone(type_id, name) => { debug!("AppEvent::ClockDone"); + + if app.notification == Notification::On { + let msg = match type_id { + ClockTypeId::Timer => { + format!("{name} stopped by reaching its maximum value.") + } + _ => format!("{:?} {name} done!", type_id), + }; + // notification + let result = notify_rust::Notification::new() + .summary(&msg.to_uppercase()) + .show(); + if let Err(err) = result { + error!("on_done {name} error: {err}"); + } + }; + #[cfg(feature = "sound")] if let Some(path) = app.sound_path.clone() { _ = Sound::new(path).and_then(|sound| sound.play()).or_else( diff --git a/src/common.rs b/src/common.rs index ee38b18..5ce64d2 100644 --- a/src/common.rs +++ b/src/common.rs @@ -17,6 +17,12 @@ pub enum Content { Pomodoro, } +#[derive(Clone, Debug)] +pub enum ClockTypeId { + Countdown, + Timer, +} + #[derive(Debug, Copy, Clone, ValueEnum, Default, Serialize, Deserialize)] pub enum Style { #[default] diff --git a/src/events.rs b/src/events.rs index bca3581..3321488 100644 --- a/src/events.rs +++ b/src/events.rs @@ -5,6 +5,7 @@ use tokio::sync::mpsc; use tokio::time::interval; use tokio_stream::{wrappers::IntervalStream, StreamMap}; +use crate::common::ClockTypeId; use crate::constants::{FPS_VALUE_MS, TICK_VALUE_MS}; #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] @@ -25,7 +26,7 @@ pub enum TuiEvent { #[derive(Clone, Debug)] pub enum AppEvent { - ClockDone, + ClockDone(ClockTypeId, String), } pub type AppEventTx = mpsc::UnboundedSender; diff --git a/src/widgets/clock.rs b/src/widgets/clock.rs index 0e12f4c..ae028aa 100644 --- a/src/widgets/clock.rs +++ b/src/widgets/clock.rs @@ -10,7 +10,7 @@ use ratatui::{ }; use crate::{ - common::Style, + common::{ClockTypeId, Style}, duration::{DurationEx, MAX_DURATION, ONE_DECI_SECOND, ONE_HOUR, ONE_MINUTE, ONE_SECOND}, events::{AppEvent, AppEventTx}, utils::center_horizontal, @@ -67,13 +67,14 @@ pub enum Format { } pub struct ClockState { + type_id: ClockTypeId, + name: Option, initial_value: DurationEx, current_value: DurationEx, tick_value: DurationEx, mode: Mode, format: Format, pub with_decis: bool, - on_done: Option>, app_tx: Option, phantom: PhantomData, } @@ -87,6 +88,19 @@ pub struct ClockStateArgs { } impl ClockState { + pub fn with_name(mut self, name: String) -> Self { + self.name = Some(name); + self + } + + pub fn get_name(&self) -> String { + self.name.clone().unwrap_or_default() + } + + pub fn get_type_id(&self) -> &ClockTypeId { + &self.type_id + } + pub fn with_mode(mut self, mode: Mode) -> Self { self.mode = mode; self @@ -313,27 +327,13 @@ impl ClockState { self.mode == Mode::Done } - pub fn with_on_done_by_condition( - mut self, - condition: bool, - handler: impl Fn() + 'static, - ) -> Self { - if condition { - self.on_done = Some(Box::new(handler)); - self - } else { - self - } - } - fn done(&mut self) { if !self.is_done() { self.mode = Mode::Done; - if let Some(handler) = &mut self.on_done { - handler(); - }; - if let Some(tx) = &mut self.app_tx { - _ = tx.send(AppEvent::ClockDone); + let type_id = self.get_type_id().clone(); + let name = self.get_name(); + if let Some(tx) = &self.app_tx { + _ = tx.send(AppEvent::ClockDone(type_id, name)); }; } } @@ -372,6 +372,8 @@ impl ClockState { app_tx, } = args; let mut instance = Self { + type_id: ClockTypeId::Countdown, + name: None, initial_value: initial_value.into(), current_value: current_value.into(), tick_value: tick_value.into(), @@ -384,7 +386,6 @@ impl ClockState { }, format: Format::S, with_decis, - on_done: None, app_tx, phantom: PhantomData, }; @@ -443,6 +444,8 @@ impl ClockState { app_tx, } = args; let mut instance = Self { + type_id: ClockTypeId::Timer, + name: None, initial_value: initial_value.into(), current_value: current_value.into(), tick_value: tick_value.into(), @@ -455,7 +458,6 @@ impl ClockState { }, format: Format::S, with_decis, - on_done: None, app_tx, phantom: PhantomData, }; diff --git a/src/widgets/clock_test.rs b/src/widgets/clock_test.rs index 4a0178f..aee20f8 100644 --- a/src/widgets/clock_test.rs +++ b/src/widgets/clock_test.rs @@ -1,36 +1,33 @@ use crate::{ + common::ClockTypeId, duration::{ONE_DECI_SECOND, ONE_HOUR, ONE_MINUTE, ONE_SECOND}, widgets::clock::*, }; use std::time::Duration; -#[test] -fn test_toggle_edit() { - let mut c = ClockState::::new(ClockStateArgs { +fn default_args() -> ClockStateArgs { + ClockStateArgs { initial_value: ONE_HOUR, current_value: ONE_HOUR, tick_value: ONE_DECI_SECOND, - with_decis: true, + with_decis: false, app_tx: None, - }); - // off by default - assert!(!c.is_edit_mode()); - // toggle on - c.toggle_edit(); - assert!(c.is_edit_mode()); - // toggle off - c.toggle_edit(); - assert!(!c.is_edit_mode()); + } +} + +#[test] +fn test_type_id() { + let c = ClockState::::new(default_args()); + assert!(matches!(c.get_type_id(), ClockTypeId::Timer)); + let c = ClockState::::new(default_args()); + assert!(matches!(c.get_type_id(), ClockTypeId::Countdown)); } #[test] fn test_default_edit_mode_hhmmss() { let mut c = ClockState::::new(ClockStateArgs { - initial_value: ONE_HOUR, - current_value: ONE_HOUR, - tick_value: ONE_DECI_SECOND, with_decis: true, - app_tx: None, + ..default_args() }); // toggle on diff --git a/src/widgets/countdown.rs b/src/widgets/countdown.rs index e560c11..e6942db 100644 --- a/src/widgets/countdown.rs +++ b/src/widgets/countdown.rs @@ -9,9 +9,7 @@ use crate::{ edit_time::{EditTimeState, EditTimeStateArgs, EditTimeWidget}, }, }; - use crossterm::event::KeyModifiers; -use notify_rust::Notification; use ratatui::{ buffer::Buffer, crossterm::event::KeyCode, @@ -19,8 +17,6 @@ use ratatui::{ text::Line, widgets::{StatefulWidget, Widget}, }; -use tracing::{debug, error}; - use std::ops::Sub; use std::{cmp::max, time::Duration}; use time::OffsetDateTime; @@ -31,7 +27,6 @@ pub struct CountdownStateArgs { pub elapsed_value: Duration, pub app_time: AppTime, pub with_decis: bool, - pub with_notification: bool, pub app_tx: AppEventTx, } @@ -52,7 +47,6 @@ impl CountdownState { initial_value, current_value, elapsed_value, - with_notification, with_decis, app_time, app_tx, @@ -65,15 +59,6 @@ impl CountdownState { tick_value: Duration::from_millis(TICK_VALUE_MS), with_decis, app_tx: Some(app_tx.clone()), - }) - .with_on_done_by_condition(with_notification, || { - debug!("on_done COUNTDOWN"); - let result = Notification::new() - .summary(&"Countdown done!".to_uppercase()) - .show(); - if let Err(err) = result { - error!("on_done COUNTDOWN error: {err}"); - } }), elapsed_clock: ClockState::::new(ClockStateArgs { initial_value: Duration::ZERO, @@ -82,6 +67,7 @@ impl CountdownState { with_decis: false, app_tx: None, }) + .with_name("MET".to_owned()) // A previous `elapsed_value > 0` means the `Clock` was running before, // but not in `Initial` state anymore. Updating `Mode` here // is needed to handle `Event::Tick` in `EventHandler::update` properly diff --git a/src/widgets/pomodoro.rs b/src/widgets/pomodoro.rs index 9d3feb9..6cfd01a 100644 --- a/src/widgets/pomodoro.rs +++ b/src/widgets/pomodoro.rs @@ -5,7 +5,6 @@ use crate::{ utils::center, widgets::clock::{ClockState, ClockStateArgs, ClockWidget, Countdown}, }; -use notify_rust::Notification; use ratatui::{ buffer::Buffer, crossterm::event::KeyCode, @@ -16,7 +15,6 @@ use ratatui::{ use serde::{Deserialize, Serialize}; use std::{cmp::max, time::Duration}; use strum::Display; -use tracing::{debug, error}; #[derive(Debug, Clone, Display, Hash, Eq, PartialEq, Deserialize, Serialize)] pub enum Mode { @@ -56,7 +54,6 @@ pub struct PomodoroStateArgs { pub initial_value_pause: Duration, pub current_value_pause: Duration, pub with_decis: bool, - pub with_notification: bool, pub app_tx: AppEventTx, } @@ -69,7 +66,6 @@ impl PomodoroState { initial_value_pause, current_value_pause, with_decis, - with_notification, app_tx, } = args; Self { @@ -82,15 +78,7 @@ impl PomodoroState { with_decis, app_tx: Some(app_tx.clone()), }) - .with_on_done_by_condition(with_notification, || { - debug!("on_done WORK"); - let result = Notification::new() - .summary(&"Work done!".to_uppercase()) - .show(); - if let Err(err) = result { - error!("on_done WORK error: {err}"); - } - }), + .with_name("Work".to_owned()), pause: ClockState::::new(ClockStateArgs { initial_value: initial_value_pause, current_value: current_value_pause, @@ -98,15 +86,7 @@ impl PomodoroState { with_decis, app_tx: Some(app_tx), }) - .with_on_done_by_condition(with_notification, || { - debug!("on_done PAUSE"); - let result = Notification::new() - .summary(&"Pause done!".to_uppercase()) - .show(); - if let Err(err) = result { - error!("on_done PAUSE error: {err}"); - } - }), + .with_name("Pause".to_owned()), }, } }