Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

### Features

- (pomodoro) `--max-rounds` arg to limit sessions [#205](https://github.com/sectore/timr-tui/pull/205)
- (notification) detailed pomodoro status in system notification [#204](https://github.com/sectore/timr-tui/pull/204)
- (cli) Add `--auto-switch` argument [#203](https://github.com/sectore/timr-tui/pull/203)

Expand Down
9 changes: 9 additions & 0 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ pub struct AppArgs {
pub pomodoro_mode: PomodoroMode,
pub pomodoro_round: u64,
pub pomodoro_auto_switch: bool,
pub pomodoro_max_rounds: Option<u64>,
pub initial_value_work: Duration,
pub current_value_work: Duration,
pub pause_duration: PauseDuration,
Expand Down Expand Up @@ -141,6 +142,11 @@ impl From<FromAppArgs> for App {
pomodoro_mode: stg.pomodoro_mode,
pomodoro_round: stg.pomodoro_count,
pomodoro_auto_switch: args.auto_switch || stg.pomodoro_auto_switch,
pomodoro_max_rounds: args
.max_rounds
// 0 -> resets `max_rounds`
.and_then(|n| (n > 0).then_some(n))
.or(stg.pomodoro_max_rounds),
initial_value_work: args.work.unwrap_or(stg.inital_value_work),
// invalidate `current_value_work` if an initial value is set via args
current_value_work: args.work.unwrap_or(stg.current_value_work),
Expand Down Expand Up @@ -184,6 +190,7 @@ impl App {
pomodoro_mode,
pomodoro_round,
pomodoro_auto_switch,
pomodoro_max_rounds,
event,
notification,
blink,
Expand Down Expand Up @@ -246,6 +253,7 @@ impl App {
app_tx: app_tx.clone(),
vim_motions,
auto_switch: pomodoro_auto_switch,
max_rounds: pomodoro_max_rounds,
}),
local_time: LocalTimeState::new(LocalTimeStateArgs {
app_time,
Expand Down Expand Up @@ -533,6 +541,7 @@ impl App {
pomodoro_mode: self.pomodoro.get_mode().clone(),
pomodoro_count: self.pomodoro.get_round(),
pomodoro_auto_switch: self.pomodoro.get_auto_switch(),
pomodoro_max_rounds: self.pomodoro.get_max_rounds(),
inital_value_work: Duration::from(*self.pomodoro.get_clock_work().get_initial_value()),
current_value_work: Duration::from(*self.pomodoro.get_clock_work().get_current_value()),
pause_duration: self.pomodoro.get_pause_duration().clone(),
Expand Down
3 changes: 3 additions & 0 deletions src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ pub struct Args {
)]
pub pause: Option<PauseDuration>,

#[arg(long, help = "Maximum number of pomodoro rounds. 0 = unlimited.")]
pub max_rounds: Option<u64>,

#[arg(long, help = "Enable auto-switch between `work` and `pause` screens.")]
pub auto_switch: bool,

Expand Down
3 changes: 3 additions & 0 deletions src/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ pub struct AppStorage {
pub pomodoro_mode: PomodoroMode,
pub pomodoro_count: u64,
pub pomodoro_auto_switch: bool,
#[serde(default)]
pub pomodoro_max_rounds: Option<u64>,
// pomodoro -> work
pub inital_value_work: Duration,
pub current_value_work: Duration,
Expand Down Expand Up @@ -79,6 +81,7 @@ impl Default for AppStorage {
pomodoro_mode: PomodoroMode::Work,
pomodoro_count: 1,
pomodoro_auto_switch: false,
pomodoro_max_rounds: None,
// pomodoro -> work
inital_value_work: DEFAULT_WORK,
current_value_work: DEFAULT_WORK,
Expand Down
112 changes: 77 additions & 35 deletions src/widgets/pomodoro.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,6 @@ use serde::{Deserialize, Serialize};
use std::{cmp::max, time::Duration};
use strum::Display;

fn work_clock_name(round: u64) -> String {
format!("work (round {round})")
}

fn pause_clock_name(round: u64, pause_duration: &PauseDuration) -> String {
format!(
"{} (round {round})",
if pause_duration.is_special_round(round) {
"pause special"
} else {
"pause"
}
)
}

#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub enum PauseDuration {
Fixed(Duration),
Expand Down Expand Up @@ -97,6 +82,7 @@ pub struct PomodoroState {
pause_duration: PauseDuration,
vim_motions: bool,
auto_switch: bool,
max_rounds: Option<u64>,
}

pub struct PomodoroStateArgs {
Expand All @@ -110,6 +96,7 @@ pub struct PomodoroStateArgs {
pub round: u64,
pub vim_motions: bool,
pub auto_switch: bool,
pub max_rounds: Option<u64>,
}

impl PomodoroState {
Expand All @@ -125,8 +112,9 @@ impl PomodoroState {
round,
vim_motions,
auto_switch,
max_rounds,
} = args;
Self {
let mut state = Self {
mode,
clock_map: ClockMap {
work: ClockState::<Countdown>::new(ClockStateArgs {
Expand All @@ -135,22 +123,23 @@ impl PomodoroState {
tick_value: Duration::from_millis(TICK_VALUE_MS),
with_decis,
app_tx: Some(app_tx.clone()),
})
.with_name(work_clock_name(round)),
}),
pause: ClockState::<Countdown>::new(ClockStateArgs {
initial_value: pause_duration.for_round(round),
current_value: current_value_pause,
tick_value: Duration::from_millis(TICK_VALUE_MS),
with_decis,
app_tx: Some(app_tx),
})
.with_name(pause_clock_name(round, &pause_duration)),
}),
},
round,
pause_duration,
vim_motions,
auto_switch,
}
max_rounds,
};
state.update_clock_names();
state
}

fn get_clock_mut(&mut self) -> &mut ClockState<Countdown> {
Expand Down Expand Up @@ -193,12 +182,55 @@ impl PomodoroState {
self.auto_switch
}

pub fn get_max_rounds(&self) -> Option<u64> {
self.max_rounds
}

fn is_last_round(&self) -> bool {
self.max_rounds.is_some_and(|m| self.round >= m)
}

#[allow(dead_code)]
pub fn is_complete(&self) -> bool {
self.is_last_round() && self.get_clock_work().is_done()
}

fn round_label(&self) -> String {
match self.max_rounds {
Some(max) => format!("round {} of {}", self.round, max),
None => format!("round {}", self.round),
}
}

fn update_work_name(&mut self) {
let name = if self.is_last_round() {
"work (last round)".to_owned()
} else {
format!("work ({})", self.round_label())
};
self.get_clock_work_mut().set_name(name);
}

fn update_pause_name(&mut self) {
let name = if self.is_last_round() {
"pause (last round)".to_owned()
} else {
format!(
"{} ({})",
if self.pause_duration.is_special_round(self.round) {
"pause special"
} else {
"pause"
},
self.round_label()
)
};
self.get_clock_pause_mut().set_name(name);
}

fn update_clock_names(&mut self) {
let round = self.round;
let work_name = work_clock_name(round);
let pause_name = pause_clock_name(round, &self.pause_duration);
self.get_clock_work_mut().set_name(work_name);
self.get_clock_pause_mut().set_name(pause_name);
self.update_work_name();
self.update_pause_name();
}

fn update_pause_initial(&mut self) {
Expand All @@ -212,12 +244,14 @@ impl PomodoroState {
}

fn next_round(&mut self) {
// increase round before (!!) updating the clock
self.round += 1;
self.update_clock_names();
self.update_pause_initial();
self.get_clock_pause_mut().reset();
self.get_clock_work_mut().reset();
if !self.is_last_round() {
// increase round before (!!) updating the clock
self.round += 1;
self.update_clock_names();
self.update_pause_initial();
self.get_clock_pause_mut().reset();
self.get_clock_work_mut().reset();
}
}

fn prev_round(&mut self) {
Expand Down Expand Up @@ -251,8 +285,10 @@ impl PomodoroState {

// Switch `Mode` automatically
fn switch_mode_auto(&mut self) {
self.switch_mode();
self.get_clock_mut().run();
if !self.is_last_round() {
self.switch_mode();
self.get_clock_mut().run();
}
}
}

Expand Down Expand Up @@ -421,7 +457,13 @@ impl StatefulWidget for PomodoroWidget {
))
.to_uppercase(),
);
let label_round = Line::raw((format!("round {}", state.get_round(),)).to_uppercase());
let label_round = Line::raw(if state.is_last_round() {
"LAST ROUND".to_owned()
} else if let Some(max) = state.get_max_rounds() {
format!("ROUND {} OF {}", state.get_round(), max)
} else {
format!("ROUND {}", state.get_round())
});

let area = area.centered(
Constraint::Length(max(
Expand Down
35 changes: 35 additions & 0 deletions src/widgets/pomodoro_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ fn args() -> PomodoroStateArgs {
round: 1,
vim_motions: false,
auto_switch: false,
max_rounds: None,
}
}

Expand All @@ -57,6 +58,40 @@ fn terminal(w: PomodoroWidget, st: PomodoroState) -> Terminal<TestBackend> {
})
}

// max rounds

#[test]
fn test_max_rounds_round_1() {
let st = st_with_args(PomodoroStateArgs {
max_rounds: Some(3),
..args()
});
let t = terminal(w(), st);
assert_snapshot!("max_rounds_round_1", t.backend());
}

#[test]
fn test_max_rounds_round_2() {
let st = st_with_args(PomodoroStateArgs {
round: 2,
max_rounds: Some(3),
..args()
});
let t = terminal(w(), st);
assert_snapshot!("max_rounds_round_2", t.backend());
}

#[test]
fn test_max_rounds_last_round() {
let st = st_with_args(PomodoroStateArgs {
round: 3,
max_rounds: Some(3),
..args()
});
let t = terminal(w(), st);
assert_snapshot!("max_rounds_last_round", t.backend());
}

// work

#[test]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
source: src/widgets/pomodoro_test.rs
expression: t.backend()
---
" "
" "
" "
" "
" "
" █████ █████ █████ █████ "
" ██ ██ ██ ██ ██ ██ ██ "
" █████ █████ ██ ██ ██ ██ "
" ██ ██ ██ ██ ██ ██ ██ "
" █████ █████ █████ █████ "
" "
" POMODORO WORK [] "
" LAST ROUND "
" "
" "
" "
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
source: src/widgets/pomodoro_test.rs
expression: t.backend()
---
" "
" "
" "
" "
" "
" █████ █████ █████ █████ "
" ██ ██ ██ ██ ██ ██ ██ "
" █████ █████ ██ ██ ██ ██ "
" ██ ██ ██ ██ ██ ██ ██ "
" █████ █████ █████ █████ "
" "
" POMODORO WORK [] "
" ROUND 1 OF 3 "
" "
" "
" "
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
source: src/widgets/pomodoro_test.rs
expression: t.backend()
---
" "
" "
" "
" "
" "
" █████ █████ █████ █████ "
" ██ ██ ██ ██ ██ ██ ██ "
" █████ █████ ██ ██ ██ ██ "
" ██ ██ ██ ██ ██ ██ ██ "
" █████ █████ █████ █████ "
" "
" POMODORO WORK [] "
" ROUND 2 OF 3 "
" "
" "
" "
Loading