fix(notification): remove callbacks in favour of mpsc messaging (#64)

This commit is contained in:
Jens Krause 2025-02-05 13:35:24 +01:00 committed by GitHub
parent 7ff167368d
commit 886deb3311
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 70 additions and 94 deletions

View File

@ -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(

View File

@ -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]

View File

@ -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<AppEvent>;

View File

@ -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<T> {
type_id: ClockTypeId,
name: Option<String>,
initial_value: DurationEx,
current_value: DurationEx,
tick_value: DurationEx,
mode: Mode,
format: Format,
pub with_decis: bool,
on_done: Option<Box<dyn Fn() + 'static>>,
app_tx: Option<AppEventTx>,
phantom: PhantomData<T>,
}
@ -87,6 +88,19 @@ pub struct ClockStateArgs {
}
impl<T> ClockState<T> {
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<T> ClockState<T> {
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<Countdown> {
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<Countdown> {
},
format: Format::S,
with_decis,
on_done: None,
app_tx,
phantom: PhantomData,
};
@ -443,6 +444,8 @@ impl ClockState<Timer> {
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<Timer> {
},
format: Format::S,
with_decis,
on_done: None,
app_tx,
phantom: PhantomData,
};

View File

@ -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::<Timer>::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::<Timer>::new(default_args());
assert!(matches!(c.get_type_id(), ClockTypeId::Timer));
let c = ClockState::<Countdown>::new(default_args());
assert!(matches!(c.get_type_id(), ClockTypeId::Countdown));
}
#[test]
fn test_default_edit_mode_hhmmss() {
let mut c = ClockState::<Timer>::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

View File

@ -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::<clock::Timer>::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

View File

@ -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::<Countdown>::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()),
},
}
}