Persist app state (#21)
This commit is contained in:
parent
2cf411e2ae
commit
c9b444e91a
13
Cargo.lock
generated
13
Cargo.lock
generated
@ -893,6 +893,18 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_json"
|
||||||
|
version = "1.0.134"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d00f4175c42ee48b15416f6193a959ba3a0d67fc699a0db9ad12df9f83991c7d"
|
||||||
|
dependencies = [
|
||||||
|
"itoa",
|
||||||
|
"memchr",
|
||||||
|
"ryu",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sharded-slab"
|
name = "sharded-slab"
|
||||||
version = "0.1.7"
|
version = "0.1.7"
|
||||||
@ -1049,6 +1061,7 @@ dependencies = [
|
|||||||
"futures",
|
"futures",
|
||||||
"ratatui",
|
"ratatui",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"strum",
|
"strum",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
|
|||||||
@ -9,6 +9,7 @@ crossterm = {version = "0.28.1", features = ["event-stream", "serde"] }
|
|||||||
color-eyre = "0.6.2"
|
color-eyre = "0.6.2"
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
serde_json = "1.0"
|
||||||
strum = { version = "0.26.3", features = ["derive"] }
|
strum = { version = "0.26.3", features = ["derive"] }
|
||||||
tokio = { version = "1.41.1", features = ["full"] }
|
tokio = { version = "1.41.1", features = ["full"] }
|
||||||
tokio-stream = "0.1.16"
|
tokio-stream = "0.1.16"
|
||||||
|
|||||||
120
src/app.rs
120
src/app.rs
@ -1,17 +1,19 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
args::{Args, ClockStyle, Content},
|
args::Args,
|
||||||
constants::TICK_VALUE_MS,
|
constants::TICK_VALUE_MS,
|
||||||
events::{Event, EventHandler, Events},
|
events::{Event, EventHandler, Events},
|
||||||
|
storage::AppStorage,
|
||||||
terminal::Terminal,
|
terminal::Terminal,
|
||||||
widgets::{
|
widgets::{
|
||||||
clock::{self, Clock, ClockArgs},
|
clock::{self, Clock, ClockArgs, Style},
|
||||||
countdown::{Countdown, CountdownWidget},
|
countdown::{Countdown, CountdownWidget},
|
||||||
footer::Footer,
|
footer::Footer,
|
||||||
header::Header,
|
header::Header,
|
||||||
pomodoro::{Pomodoro, PomodoroArgs, PomodoroWidget},
|
pomodoro::{Mode as PomodoroMode, Pomodoro, PomodoroArgs, PomodoroWidget},
|
||||||
timer::{Timer, TimerWidget},
|
timer::{Timer, TimerWidget},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
use clap::ValueEnum;
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
buffer::Buffer,
|
buffer::Buffer,
|
||||||
@ -19,9 +21,23 @@ use ratatui::{
|
|||||||
layout::{Constraint, Layout, Rect},
|
layout::{Constraint, Layout, Rect},
|
||||||
widgets::{StatefulWidget, Widget},
|
widgets::{StatefulWidget, Widget},
|
||||||
};
|
};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
|
#[derive(
|
||||||
|
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Default, Serialize, Deserialize,
|
||||||
|
)]
|
||||||
|
pub enum Content {
|
||||||
|
#[default]
|
||||||
|
#[value(name = "countdown", alias = "c")]
|
||||||
|
Countdown,
|
||||||
|
#[value(name = "timer", alias = "t")]
|
||||||
|
Timer,
|
||||||
|
#[value(name = "pomodoro", alias = "p")]
|
||||||
|
Pomodoro,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
enum Mode {
|
enum Mode {
|
||||||
Running,
|
Running,
|
||||||
@ -36,49 +52,92 @@ pub struct App {
|
|||||||
countdown: Countdown,
|
countdown: Countdown,
|
||||||
timer: Timer,
|
timer: Timer,
|
||||||
pomodoro: Pomodoro,
|
pomodoro: Pomodoro,
|
||||||
clock_style: ClockStyle,
|
style: Style,
|
||||||
with_decis: bool,
|
with_decis: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct AppArgs {
|
||||||
|
pub style: Style,
|
||||||
|
pub with_decis: bool,
|
||||||
|
pub content: Content,
|
||||||
|
pub pomodoro_mode: PomodoroMode,
|
||||||
|
pub initial_value_work: Duration,
|
||||||
|
pub current_value_work: Duration,
|
||||||
|
pub initial_value_pause: Duration,
|
||||||
|
pub current_value_pause: Duration,
|
||||||
|
pub initial_value_countdown: Duration,
|
||||||
|
pub current_value_countdown: Duration,
|
||||||
|
pub current_value_timer: Duration,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Getting `AppArgs` by merging `Args` and `AppStorage`.
|
||||||
|
/// `Args` wins btw.
|
||||||
|
impl From<(Args, AppStorage)> for AppArgs {
|
||||||
|
fn from((args, stg): (Args, AppStorage)) -> Self {
|
||||||
|
AppArgs {
|
||||||
|
with_decis: args.decis || stg.with_decis,
|
||||||
|
content: args.mode.unwrap_or(stg.content),
|
||||||
|
style: args.style.unwrap_or(stg.style),
|
||||||
|
pomodoro_mode: stg.pomodoro_mode,
|
||||||
|
initial_value_work: args.work.unwrap_or(stg.inital_value_work),
|
||||||
|
current_value_work: stg.current_value_work,
|
||||||
|
initial_value_pause: args.pause,
|
||||||
|
current_value_pause: stg.current_value_pause,
|
||||||
|
initial_value_countdown: args.countdown,
|
||||||
|
current_value_countdown: stg.current_value_countdown,
|
||||||
|
current_value_timer: stg.current_value_timer,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
pub fn new(args: Args) -> Self {
|
pub fn new(args: AppArgs) -> Self {
|
||||||
let Args {
|
let AppArgs {
|
||||||
style,
|
style,
|
||||||
work: work_initial_value,
|
initial_value_work,
|
||||||
pause: pause_initial_value,
|
initial_value_pause,
|
||||||
mode: content,
|
initial_value_countdown,
|
||||||
countdown: countdown_initial_value,
|
current_value_work,
|
||||||
decis: with_decis,
|
current_value_pause,
|
||||||
..
|
current_value_countdown,
|
||||||
|
current_value_timer,
|
||||||
|
content,
|
||||||
|
with_decis,
|
||||||
|
pomodoro_mode,
|
||||||
} = args;
|
} = args;
|
||||||
Self {
|
Self {
|
||||||
mode: Mode::Running,
|
mode: Mode::Running,
|
||||||
content,
|
content,
|
||||||
show_menu: false,
|
show_menu: false,
|
||||||
clock_style: style,
|
style,
|
||||||
with_decis,
|
with_decis,
|
||||||
countdown: Countdown::new(Clock::<clock::Countdown>::new(ClockArgs {
|
countdown: Countdown::new(Clock::<clock::Countdown>::new(ClockArgs {
|
||||||
initial_value: countdown_initial_value,
|
initial_value: initial_value_countdown,
|
||||||
|
current_value: current_value_countdown,
|
||||||
tick_value: Duration::from_millis(TICK_VALUE_MS),
|
tick_value: Duration::from_millis(TICK_VALUE_MS),
|
||||||
style,
|
style,
|
||||||
with_decis,
|
with_decis,
|
||||||
})),
|
})),
|
||||||
timer: Timer::new(Clock::<clock::Timer>::new(ClockArgs {
|
timer: Timer::new(Clock::<clock::Timer>::new(ClockArgs {
|
||||||
initial_value: Duration::ZERO,
|
initial_value: Duration::ZERO,
|
||||||
|
current_value: current_value_timer,
|
||||||
tick_value: Duration::from_millis(TICK_VALUE_MS),
|
tick_value: Duration::from_millis(TICK_VALUE_MS),
|
||||||
style,
|
style,
|
||||||
with_decis,
|
with_decis,
|
||||||
})),
|
})),
|
||||||
pomodoro: Pomodoro::new(PomodoroArgs {
|
pomodoro: Pomodoro::new(PomodoroArgs {
|
||||||
work: work_initial_value,
|
mode: pomodoro_mode,
|
||||||
pause: pause_initial_value,
|
initial_value_work,
|
||||||
|
current_value_work,
|
||||||
|
initial_value_pause,
|
||||||
|
current_value_pause,
|
||||||
style,
|
style,
|
||||||
with_decis,
|
with_decis,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn run(&mut self, mut terminal: Terminal, mut events: Events) -> Result<()> {
|
pub async fn run(mut self, mut terminal: Terminal, mut events: Events) -> Result<Self> {
|
||||||
while self.is_running() {
|
while self.is_running() {
|
||||||
if let Some(event) = events.next().await {
|
if let Some(event) = events.next().await {
|
||||||
// Pipe events into subviews and handle only 'unhandled' events afterwards
|
// Pipe events into subviews and handle only 'unhandled' events afterwards
|
||||||
@ -97,7 +156,7 @@ impl App {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_running(&self) -> bool {
|
fn is_running(&self) -> bool {
|
||||||
@ -113,11 +172,11 @@ impl App {
|
|||||||
KeyCode::Char('p') => self.content = Content::Pomodoro,
|
KeyCode::Char('p') => self.content = Content::Pomodoro,
|
||||||
KeyCode::Char('m') => self.show_menu = !self.show_menu,
|
KeyCode::Char('m') => self.show_menu = !self.show_menu,
|
||||||
KeyCode::Char(',') => {
|
KeyCode::Char(',') => {
|
||||||
self.clock_style = self.clock_style.next();
|
self.style = self.style.next();
|
||||||
// update clocks
|
// update clocks
|
||||||
self.timer.set_style(self.clock_style);
|
self.timer.set_style(self.style);
|
||||||
self.countdown.set_style(self.clock_style);
|
self.countdown.set_style(self.style);
|
||||||
self.pomodoro.set_style(self.clock_style);
|
self.pomodoro.set_style(self.style);
|
||||||
}
|
}
|
||||||
KeyCode::Char('.') => {
|
KeyCode::Char('.') => {
|
||||||
self.with_decis = !self.with_decis;
|
self.with_decis = !self.with_decis;
|
||||||
@ -138,6 +197,23 @@ impl App {
|
|||||||
})?;
|
})?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn to_storage(&self) -> AppStorage {
|
||||||
|
AppStorage {
|
||||||
|
content: self.content,
|
||||||
|
show_menu: self.show_menu,
|
||||||
|
style: self.style,
|
||||||
|
with_decis: self.with_decis,
|
||||||
|
pomodoro_mode: self.pomodoro.get_mode().clone(),
|
||||||
|
inital_value_work: self.pomodoro.get_clock_work().initial_value,
|
||||||
|
current_value_work: self.pomodoro.get_clock_work().current_value,
|
||||||
|
inital_value_pause: self.pomodoro.get_clock_pause().initial_value,
|
||||||
|
current_value_pause: self.pomodoro.get_clock_pause().current_value,
|
||||||
|
inital_value_countdown: self.countdown.get_clock().initial_value,
|
||||||
|
current_value_countdown: self.countdown.get_clock().current_value,
|
||||||
|
current_value_timer: self.timer.get_clock().current_value,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct AppWidget;
|
struct AppWidget;
|
||||||
|
|||||||
59
src/args.rs
59
src/args.rs
@ -1,42 +1,11 @@
|
|||||||
use clap::{Parser, ValueEnum};
|
use clap::Parser;
|
||||||
use color_eyre::{
|
use color_eyre::{
|
||||||
eyre::{ensure, eyre},
|
eyre::{ensure, eyre},
|
||||||
Report,
|
Report,
|
||||||
};
|
};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
|
use crate::{app::Content, widgets::clock::Style};
|
||||||
pub enum Content {
|
|
||||||
#[value(name = "countdown", alias = "c")]
|
|
||||||
Countdown,
|
|
||||||
#[value(name = "timer", alias = "t")]
|
|
||||||
Timer,
|
|
||||||
#[value(name = "pomodoro", alias = "p")]
|
|
||||||
Pomodoro,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, ValueEnum)]
|
|
||||||
pub enum ClockStyle {
|
|
||||||
#[value(name = "bold", alias = "b")]
|
|
||||||
Bold,
|
|
||||||
#[value(name = "empty", alias = "e")]
|
|
||||||
Empty,
|
|
||||||
#[value(name = "thick", alias = "t")]
|
|
||||||
Thick,
|
|
||||||
#[value(name = "cross", alias = "c")]
|
|
||||||
Cross,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ClockStyle {
|
|
||||||
pub fn next(&self) -> Self {
|
|
||||||
match self {
|
|
||||||
ClockStyle::Bold => ClockStyle::Empty,
|
|
||||||
ClockStyle::Empty => ClockStyle::Thick,
|
|
||||||
ClockStyle::Thick => ClockStyle::Cross,
|
|
||||||
ClockStyle::Cross => ClockStyle::Bold,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
pub struct Args {
|
pub struct Args {
|
||||||
@ -45,41 +14,38 @@ pub struct Args {
|
|||||||
help = "Countdown time to start from. Format: 'ss', 'mm:ss', or 'hh:mm:ss'"
|
help = "Countdown time to start from. Format: 'ss', 'mm:ss', or 'hh:mm:ss'"
|
||||||
)]
|
)]
|
||||||
pub countdown: Duration,
|
pub countdown: Duration,
|
||||||
|
|
||||||
#[arg(long, short, value_parser = parse_duration,
|
#[arg(long, short, value_parser = parse_duration,
|
||||||
default_value="25:00" /* 25min */,
|
|
||||||
help = "Work time to count down from. Format: 'ss', 'mm:ss', or 'hh:mm:ss'"
|
help = "Work time to count down from. Format: 'ss', 'mm:ss', or 'hh:mm:ss'"
|
||||||
)]
|
)]
|
||||||
pub work: Duration,
|
pub work: Option<Duration>,
|
||||||
|
|
||||||
#[arg(long, short, value_parser = parse_duration,
|
#[arg(long, short, value_parser = parse_duration,
|
||||||
default_value="5:00" /* 5min */,
|
default_value="5:00" /* 5min */,
|
||||||
help = "Pause time to count down from. Format: 'ss', 'mm:ss', or 'hh:mm:ss'"
|
help = "Pause time to count down from. Format: 'ss', 'mm:ss', or 'hh:mm:ss'"
|
||||||
)]
|
)]
|
||||||
pub pause: Duration,
|
pub pause: Duration,
|
||||||
|
|
||||||
#[arg(
|
#[arg(long, short = 'd', help = "Whether to show deciseconds or not")]
|
||||||
long,
|
|
||||||
short = 'd',
|
|
||||||
default_value = "false",
|
|
||||||
help = "Wether to show deciseconds or not"
|
|
||||||
)]
|
|
||||||
pub decis: bool,
|
pub decis: bool,
|
||||||
|
|
||||||
#[arg(
|
#[arg(
|
||||||
short = 'm',
|
short = 'm',
|
||||||
value_enum,
|
value_enum,
|
||||||
default_value = "timer",
|
|
||||||
help = "Mode to start with: [t]imer, [c]ountdown, [p]omodoro"
|
help = "Mode to start with: [t]imer, [c]ountdown, [p]omodoro"
|
||||||
)]
|
)]
|
||||||
pub mode: Content,
|
pub mode: Option<Content>,
|
||||||
|
|
||||||
#[arg(
|
#[arg(
|
||||||
long,
|
long,
|
||||||
short = 's',
|
short = 's',
|
||||||
value_enum,
|
value_enum,
|
||||||
default_value = "bold",
|
|
||||||
help = "Style to display time with: [b]old, [t]hick, [c]ross, [e]mpty"
|
help = "Style to display time with: [b]old, [t]hick, [c]ross, [e]mpty"
|
||||||
)]
|
)]
|
||||||
pub style: ClockStyle,
|
pub style: Option<Style>,
|
||||||
|
|
||||||
|
#[arg(long, short = 'r', help = "Reset stored values to default")]
|
||||||
|
pub reset: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_duration(arg: &str) -> Result<Duration, Report> {
|
fn parse_duration(arg: &str) -> Result<Duration, Report> {
|
||||||
@ -130,19 +96,16 @@ mod tests {
|
|||||||
fn test_parse_duration() {
|
fn test_parse_duration() {
|
||||||
// ss
|
// ss
|
||||||
assert_eq!(parse_duration("50").unwrap(), Duration::from_secs(50));
|
assert_eq!(parse_duration("50").unwrap(), Duration::from_secs(50));
|
||||||
|
|
||||||
// mm:ss
|
// mm:ss
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_duration("01:30").unwrap(),
|
parse_duration("01:30").unwrap(),
|
||||||
Duration::from_secs(60 + 30)
|
Duration::from_secs(60 + 30)
|
||||||
);
|
);
|
||||||
|
|
||||||
// hh:mm:ss
|
// hh:mm:ss
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_duration("01:30:00").unwrap(),
|
parse_duration("01:30:00").unwrap(),
|
||||||
Duration::from_secs(60 * 60 + 30 * 60)
|
Duration::from_secs(60 * 60 + 30 * 60)
|
||||||
);
|
);
|
||||||
|
|
||||||
// errors
|
// errors
|
||||||
assert!(parse_duration("1:60").is_err()); // invalid seconds
|
assert!(parse_duration("1:60").is_err()); // invalid seconds
|
||||||
assert!(parse_duration("60:00").is_err()); // invalid minutes
|
assert!(parse_duration("60:00").is_err()); // invalid minutes
|
||||||
|
|||||||
@ -5,18 +5,20 @@ use std::fs;
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub log_dir: PathBuf,
|
pub log_dir: PathBuf,
|
||||||
|
pub data_dir: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
pub fn init() -> Result<Self> {
|
pub fn init() -> Result<Self> {
|
||||||
let log_dir = get_default_state_dir()?.join("logs");
|
let log_dir = get_default_state_dir()?.join("logs");
|
||||||
fs::create_dir_all(&log_dir)?;
|
fs::create_dir_all(&log_dir)?;
|
||||||
|
let data_dir = get_default_state_dir()?.join("data");
|
||||||
|
fs::create_dir_all(&data_dir)?;
|
||||||
|
|
||||||
Ok(Self { log_dir })
|
Ok(Self { log_dir, data_dir })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// fn new
|
|
||||||
pub fn get_project_dir() -> Result<ProjectDirs> {
|
pub fn get_project_dir() -> Result<ProjectDirs> {
|
||||||
let dirs = ProjectDirs::from("", "", APP_NAME)
|
let dirs = ProjectDirs::from("", "", APP_NAME)
|
||||||
.ok_or_else(|| eyre!("Failed to get project directories"))?;
|
.ok_or_else(|| eyre!("Failed to get project directories"))?;
|
||||||
|
|||||||
29
src/main.rs
29
src/main.rs
@ -6,6 +6,7 @@ mod events;
|
|||||||
mod logging;
|
mod logging;
|
||||||
|
|
||||||
mod args;
|
mod args;
|
||||||
|
mod storage;
|
||||||
mod terminal;
|
mod terminal;
|
||||||
mod utils;
|
mod utils;
|
||||||
mod widgets;
|
mod widgets;
|
||||||
@ -14,20 +15,38 @@ use app::App;
|
|||||||
use args::Args;
|
use args::Args;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use color_eyre::Result;
|
use color_eyre::Result;
|
||||||
|
use config::Config;
|
||||||
|
use storage::{AppStorage, Storage};
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
let config = config::Config::init()?;
|
let Config { log_dir, data_dir } = Config::init()?;
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
logging::Logger::new(config.log_dir).init()?;
|
logging::Logger::new(log_dir).init()?;
|
||||||
|
|
||||||
color_eyre::install()?;
|
color_eyre::install()?;
|
||||||
|
|
||||||
let args = Args::parse();
|
|
||||||
|
|
||||||
let terminal = terminal::setup()?;
|
let terminal = terminal::setup()?;
|
||||||
let events = events::Events::new();
|
let events = events::Events::new();
|
||||||
App::new(args).run(terminal, events).await?;
|
|
||||||
|
// get args given by CLI
|
||||||
|
let args = Args::parse();
|
||||||
|
|
||||||
|
// check persistant storage
|
||||||
|
let storage = Storage::new(data_dir);
|
||||||
|
// option to reset previous stored data to `default`
|
||||||
|
let stg = if args.reset {
|
||||||
|
AppStorage::default()
|
||||||
|
} else {
|
||||||
|
storage.load().unwrap_or_default()
|
||||||
|
};
|
||||||
|
|
||||||
|
// merge `Args` and `AppStorage`.
|
||||||
|
let app_args = (args, stg).into();
|
||||||
|
let app_storage = App::new(app_args).run(terminal, events).await?.to_storage();
|
||||||
|
// store app state persistantly
|
||||||
|
storage.save(app_storage)?;
|
||||||
|
|
||||||
terminal::teardown()?;
|
terminal::teardown()?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
82
src/storage.rs
Normal file
82
src/storage.rs
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
use crate::{
|
||||||
|
app::Content,
|
||||||
|
constants::APP_NAME,
|
||||||
|
widgets::{clock::Style, pomodoro::Mode as PomodoroMode},
|
||||||
|
};
|
||||||
|
use color_eyre::eyre::Result;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::fs;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct AppStorage {
|
||||||
|
pub content: Content,
|
||||||
|
pub show_menu: bool,
|
||||||
|
pub style: Style,
|
||||||
|
pub with_decis: bool,
|
||||||
|
pub pomodoro_mode: PomodoroMode,
|
||||||
|
// pomodoro -> work
|
||||||
|
pub inital_value_work: Duration,
|
||||||
|
pub current_value_work: Duration,
|
||||||
|
// pomodoro -> pause
|
||||||
|
pub inital_value_pause: Duration,
|
||||||
|
pub current_value_pause: Duration,
|
||||||
|
// countdown
|
||||||
|
pub inital_value_countdown: Duration,
|
||||||
|
pub current_value_countdown: Duration,
|
||||||
|
// timer
|
||||||
|
pub current_value_timer: Duration,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for AppStorage {
|
||||||
|
fn default() -> Self {
|
||||||
|
const DEFAULT_WORK: Duration = Duration::from_secs(60 * 25); /* 25min */
|
||||||
|
const DEFAULT_PAUSE: Duration = Duration::from_secs(60 * 5); /* 5min */
|
||||||
|
const DEFAULT_COUNTDOWN: Duration = Duration::from_secs(60 * 10); /* 10min */
|
||||||
|
AppStorage {
|
||||||
|
content: Content::default(),
|
||||||
|
show_menu: false,
|
||||||
|
style: Style::default(),
|
||||||
|
with_decis: false,
|
||||||
|
pomodoro_mode: PomodoroMode::Work,
|
||||||
|
// pomodoro -> work
|
||||||
|
inital_value_work: DEFAULT_WORK,
|
||||||
|
current_value_work: DEFAULT_WORK,
|
||||||
|
// pomodoro -> pause
|
||||||
|
inital_value_pause: DEFAULT_PAUSE,
|
||||||
|
current_value_pause: DEFAULT_PAUSE,
|
||||||
|
// countdown
|
||||||
|
inital_value_countdown: DEFAULT_COUNTDOWN,
|
||||||
|
current_value_countdown: DEFAULT_COUNTDOWN,
|
||||||
|
// timer
|
||||||
|
current_value_timer: Duration::ZERO,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Storage {
|
||||||
|
data_dir: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Storage {
|
||||||
|
pub fn new(data_dir: PathBuf) -> Self {
|
||||||
|
Self { data_dir }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_storage_path(&self) -> PathBuf {
|
||||||
|
self.data_dir.join(format!("{}.data", APP_NAME))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn save(&self, data: AppStorage) -> Result<()> {
|
||||||
|
let file = fs::File::create(self.get_storage_path())?;
|
||||||
|
serde_json::to_writer(file, &data)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load(&self) -> Result<AppStorage> {
|
||||||
|
let file = fs::File::open(self.get_storage_path())?;
|
||||||
|
let data = serde_json::from_reader(file)?;
|
||||||
|
Ok(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,3 +1,5 @@
|
|||||||
|
use clap::ValueEnum;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
@ -9,7 +11,7 @@ use ratatui::{
|
|||||||
widgets::StatefulWidget,
|
widgets::StatefulWidget,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{args::ClockStyle, utils::center_horizontal};
|
use crate::utils::center_horizontal;
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Display, PartialEq, Eq)]
|
#[derive(Debug, Copy, Clone, Display, PartialEq, Eq)]
|
||||||
pub enum Time {
|
pub enum Time {
|
||||||
@ -74,22 +76,47 @@ pub enum Format {
|
|||||||
HhMmSs,
|
HhMmSs,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, ValueEnum, Default, Serialize, Deserialize)]
|
||||||
|
pub enum Style {
|
||||||
|
#[default]
|
||||||
|
#[value(name = "bold", alias = "b")]
|
||||||
|
Bold,
|
||||||
|
#[value(name = "empty", alias = "e")]
|
||||||
|
Empty,
|
||||||
|
#[value(name = "thick", alias = "t")]
|
||||||
|
Thick,
|
||||||
|
#[value(name = "cross", alias = "c")]
|
||||||
|
Cross,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Style {
|
||||||
|
pub fn next(&self) -> Self {
|
||||||
|
match self {
|
||||||
|
Style::Bold => Style::Empty,
|
||||||
|
Style::Empty => Style::Thick,
|
||||||
|
Style::Thick => Style::Cross,
|
||||||
|
Style::Cross => Style::Bold,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Clock<T> {
|
pub struct Clock<T> {
|
||||||
initial_value: Duration,
|
pub initial_value: Duration,
|
||||||
|
pub current_value: Duration,
|
||||||
tick_value: Duration,
|
tick_value: Duration,
|
||||||
current_value: Duration,
|
|
||||||
mode: Mode,
|
mode: Mode,
|
||||||
format: Format,
|
format: Format,
|
||||||
pub style: ClockStyle,
|
pub style: Style,
|
||||||
pub with_decis: bool,
|
pub with_decis: bool,
|
||||||
phantom: PhantomData<T>,
|
phantom: PhantomData<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ClockArgs {
|
pub struct ClockArgs {
|
||||||
pub initial_value: Duration,
|
pub initial_value: Duration,
|
||||||
|
pub current_value: Duration,
|
||||||
pub tick_value: Duration,
|
pub tick_value: Duration,
|
||||||
pub style: ClockStyle,
|
pub style: Style,
|
||||||
pub with_decis: bool,
|
pub with_decis: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -315,15 +342,22 @@ impl Clock<Countdown> {
|
|||||||
pub fn new(args: ClockArgs) -> Self {
|
pub fn new(args: ClockArgs) -> Self {
|
||||||
let ClockArgs {
|
let ClockArgs {
|
||||||
initial_value,
|
initial_value,
|
||||||
|
current_value,
|
||||||
tick_value,
|
tick_value,
|
||||||
style,
|
style,
|
||||||
with_decis,
|
with_decis,
|
||||||
} = args;
|
} = args;
|
||||||
let mut instance = Self {
|
let mut instance = Self {
|
||||||
initial_value,
|
initial_value,
|
||||||
|
current_value,
|
||||||
tick_value,
|
tick_value,
|
||||||
current_value: initial_value,
|
mode: if current_value == Duration::ZERO {
|
||||||
mode: Mode::Initial,
|
Mode::Done
|
||||||
|
} else if current_value == initial_value {
|
||||||
|
Mode::Initial
|
||||||
|
} else {
|
||||||
|
Mode::Pause
|
||||||
|
},
|
||||||
format: Format::S,
|
format: Format::S,
|
||||||
style,
|
style,
|
||||||
with_decis,
|
with_decis,
|
||||||
@ -380,15 +414,22 @@ impl Clock<Timer> {
|
|||||||
pub fn new(args: ClockArgs) -> Self {
|
pub fn new(args: ClockArgs) -> Self {
|
||||||
let ClockArgs {
|
let ClockArgs {
|
||||||
initial_value,
|
initial_value,
|
||||||
|
current_value,
|
||||||
tick_value,
|
tick_value,
|
||||||
style,
|
style,
|
||||||
with_decis,
|
with_decis,
|
||||||
} = args;
|
} = args;
|
||||||
let mut instance = Self {
|
let mut instance = Self {
|
||||||
initial_value,
|
initial_value,
|
||||||
|
current_value,
|
||||||
tick_value,
|
tick_value,
|
||||||
current_value: Duration::ZERO,
|
mode: if current_value == initial_value {
|
||||||
mode: Mode::Initial,
|
Mode::Initial
|
||||||
|
} else if current_value >= MAX_DURATION {
|
||||||
|
Mode::Done
|
||||||
|
} else {
|
||||||
|
Mode::Pause
|
||||||
|
},
|
||||||
format: Format::S,
|
format: Format::S,
|
||||||
phantom: PhantomData,
|
phantom: PhantomData,
|
||||||
style,
|
style,
|
||||||
@ -552,12 +593,12 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_digit_symbol(&self, style: &ClockStyle) -> &str {
|
fn get_digit_symbol(&self, style: &Style) -> &str {
|
||||||
match &style {
|
match &style {
|
||||||
ClockStyle::Bold => "█",
|
Style::Bold => "█",
|
||||||
ClockStyle::Empty => "░",
|
Style::Empty => "░",
|
||||||
ClockStyle::Cross => "╬",
|
Style::Cross => "╬",
|
||||||
ClockStyle::Thick => "┃",
|
Style::Thick => "┃",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -8,10 +8,9 @@ use ratatui::{
|
|||||||
use std::cmp::max;
|
use std::cmp::max;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
args::ClockStyle,
|
|
||||||
events::{Event, EventHandler},
|
events::{Event, EventHandler},
|
||||||
utils::center,
|
utils::center,
|
||||||
widgets::clock::{self, Clock, ClockWidget},
|
widgets::clock::{self, Clock, ClockWidget, Style},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@ -24,13 +23,17 @@ impl Countdown {
|
|||||||
Self { clock }
|
Self { clock }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_style(&mut self, style: ClockStyle) {
|
pub fn set_style(&mut self, style: Style) {
|
||||||
self.clock.style = style;
|
self.clock.style = style;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_with_decis(&mut self, with_decis: bool) {
|
pub fn set_with_decis(&mut self, with_decis: bool) {
|
||||||
self.clock.with_decis = with_decis;
|
self.clock.with_decis = with_decis;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_clock(&self) -> &Clock<clock::Countdown> {
|
||||||
|
&self.clock
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventHandler for Countdown {
|
impl EventHandler for Countdown {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
use crate::args::Content;
|
use crate::app::Content;
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
buffer::Buffer,
|
buffer::Buffer,
|
||||||
layout::{Constraint, Layout, Rect},
|
layout::{Constraint, Layout, Rect},
|
||||||
|
|||||||
@ -1,9 +1,8 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
args::ClockStyle,
|
|
||||||
constants::TICK_VALUE_MS,
|
constants::TICK_VALUE_MS,
|
||||||
events::{Event, EventHandler},
|
events::{Event, EventHandler},
|
||||||
utils::center,
|
utils::center,
|
||||||
widgets::clock::{Clock, ClockWidget, Countdown},
|
widgets::clock::{Clock, ClockWidget, Countdown, Style},
|
||||||
};
|
};
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
buffer::Buffer,
|
buffer::Buffer,
|
||||||
@ -16,10 +15,12 @@ use std::{cmp::max, time::Duration};
|
|||||||
|
|
||||||
use strum::Display;
|
use strum::Display;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use super::clock::ClockArgs;
|
use super::clock::ClockArgs;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Display, Hash, Eq, PartialEq)]
|
#[derive(Debug, Clone, Display, Hash, Eq, PartialEq, Deserialize, Serialize)]
|
||||||
enum Mode {
|
pub enum Mode {
|
||||||
Work,
|
Work,
|
||||||
Pause,
|
Pause,
|
||||||
}
|
}
|
||||||
@ -46,31 +47,39 @@ pub struct Pomodoro {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct PomodoroArgs {
|
pub struct PomodoroArgs {
|
||||||
pub work: Duration,
|
pub mode: Mode,
|
||||||
pub pause: Duration,
|
pub initial_value_work: Duration,
|
||||||
pub style: ClockStyle,
|
pub current_value_work: Duration,
|
||||||
|
pub initial_value_pause: Duration,
|
||||||
|
pub current_value_pause: Duration,
|
||||||
|
pub style: Style,
|
||||||
pub with_decis: bool,
|
pub with_decis: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Pomodoro {
|
impl Pomodoro {
|
||||||
pub fn new(args: PomodoroArgs) -> Self {
|
pub fn new(args: PomodoroArgs) -> Self {
|
||||||
let PomodoroArgs {
|
let PomodoroArgs {
|
||||||
work,
|
mode,
|
||||||
pause,
|
initial_value_work,
|
||||||
|
current_value_work,
|
||||||
|
initial_value_pause,
|
||||||
|
current_value_pause,
|
||||||
style,
|
style,
|
||||||
with_decis,
|
with_decis,
|
||||||
} = args;
|
} = args;
|
||||||
Self {
|
Self {
|
||||||
mode: Mode::Work,
|
mode,
|
||||||
clock_map: ClockMap {
|
clock_map: ClockMap {
|
||||||
work: Clock::<Countdown>::new(ClockArgs {
|
work: Clock::<Countdown>::new(ClockArgs {
|
||||||
initial_value: work,
|
initial_value: initial_value_work,
|
||||||
|
current_value: current_value_work,
|
||||||
tick_value: Duration::from_millis(TICK_VALUE_MS),
|
tick_value: Duration::from_millis(TICK_VALUE_MS),
|
||||||
style,
|
style,
|
||||||
with_decis,
|
with_decis,
|
||||||
}),
|
}),
|
||||||
pause: Clock::<Countdown>::new(ClockArgs {
|
pause: Clock::<Countdown>::new(ClockArgs {
|
||||||
initial_value: pause,
|
initial_value: initial_value_pause,
|
||||||
|
current_value: current_value_pause,
|
||||||
tick_value: Duration::from_millis(TICK_VALUE_MS),
|
tick_value: Duration::from_millis(TICK_VALUE_MS),
|
||||||
style,
|
style,
|
||||||
with_decis,
|
with_decis,
|
||||||
@ -79,11 +88,23 @@ impl Pomodoro {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_clock(&mut self) -> &mut Clock<Countdown> {
|
pub fn get_clock(&mut self) -> &mut Clock<Countdown> {
|
||||||
self.clock_map.get(&self.mode)
|
self.clock_map.get(&self.mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_style(&mut self, style: crate::args::ClockStyle) {
|
pub fn get_clock_work(&self) -> &Clock<Countdown> {
|
||||||
|
&self.clock_map.work
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_clock_pause(&self) -> &Clock<Countdown> {
|
||||||
|
&self.clock_map.pause
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_mode(&self) -> &Mode {
|
||||||
|
&self.mode
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_style(&mut self, style: Style) {
|
||||||
self.clock_map.work.style = style;
|
self.clock_map.work.style = style;
|
||||||
self.clock_map.pause.style = style;
|
self.clock_map.pause.style = style;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
args::ClockStyle,
|
|
||||||
events::{Event, EventHandler},
|
events::{Event, EventHandler},
|
||||||
utils::center,
|
utils::center,
|
||||||
widgets::clock::{self, Clock, ClockWidget},
|
widgets::clock::{self, Clock, ClockWidget, Style},
|
||||||
};
|
};
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
buffer::Buffer,
|
buffer::Buffer,
|
||||||
@ -23,13 +22,17 @@ impl Timer {
|
|||||||
Self { clock }
|
Self { clock }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_style(&mut self, style: ClockStyle) {
|
pub fn set_style(&mut self, style: Style) {
|
||||||
self.clock.style = style;
|
self.clock.style = style;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_with_decis(&mut self, with_decis: bool) {
|
pub fn set_with_decis(&mut self, with_decis: bool) {
|
||||||
self.clock.with_decis = with_decis;
|
self.clock.with_decis = with_decis;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_clock(&self) -> &Clock<clock::Timer> {
|
||||||
|
&self.clock
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventHandler for Timer {
|
impl EventHandler for Timer {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user