refactor(footer): AppTimeFormat data handling (#89)
- Extract local state of `app_time_format` from `footer` to have it globally available - Add a deserialization fallback for deprecated `AppTimeFormat::Hidden` - Persist `footer_app_time` toggle state
This commit is contained in:
parent
637c1da21b
commit
c494f0e829
38
src/app.rs
38
src/app.rs
@ -44,6 +44,7 @@ pub struct App {
|
||||
#[allow(dead_code)] // w/ `--features sound` available only
|
||||
sound_path: Option<PathBuf>,
|
||||
app_time: AppTime,
|
||||
app_time_format: AppTimeFormat,
|
||||
countdown: CountdownState,
|
||||
timer: TimerState,
|
||||
pomodoro: PomodoroState,
|
||||
@ -72,6 +73,7 @@ pub struct AppArgs {
|
||||
pub current_value_timer: Duration,
|
||||
pub app_tx: events::AppEventTx,
|
||||
pub sound_path: Option<PathBuf>,
|
||||
pub footer_toggle_app_time: Toggle,
|
||||
}
|
||||
|
||||
pub struct FromAppArgs {
|
||||
@ -131,6 +133,7 @@ impl From<FromAppArgs> for App {
|
||||
sound_path: args.sound,
|
||||
#[cfg(not(feature = "sound"))]
|
||||
sound_path: None,
|
||||
footer_toggle_app_time: stg.footer_app_time,
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -164,6 +167,7 @@ impl App {
|
||||
blink,
|
||||
sound_path,
|
||||
app_tx,
|
||||
footer_toggle_app_time,
|
||||
} = args;
|
||||
let app_time = get_app_time();
|
||||
|
||||
@ -174,6 +178,7 @@ impl App {
|
||||
sound_path,
|
||||
content,
|
||||
app_time,
|
||||
app_time_format,
|
||||
style,
|
||||
with_decis,
|
||||
countdown: CountdownState::new(CountdownStateArgs {
|
||||
@ -204,7 +209,14 @@ impl App {
|
||||
round: pomodoro_round,
|
||||
app_tx: app_tx.clone(),
|
||||
}),
|
||||
footer: FooterState::new(show_menu, app_time_format),
|
||||
footer: FooterState::new(
|
||||
show_menu,
|
||||
if footer_toggle_app_time == Toggle::On {
|
||||
Some(app_time_format)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@ -222,7 +234,26 @@ impl App {
|
||||
KeyCode::Char('t') => app.content = Content::Timer,
|
||||
KeyCode::Char('p') => app.content = Content::Pomodoro,
|
||||
// toogle app time format
|
||||
KeyCode::Char(':') => app.footer.toggle_app_time_format(),
|
||||
KeyCode::Char(':') => {
|
||||
//
|
||||
// TODO: Check content != LocalClock
|
||||
let new_format = match app.footer.app_time_format() {
|
||||
// footer is hidden in footer ->
|
||||
None => Some(AppTimeFormat::first()),
|
||||
Some(v) => {
|
||||
if v != &AppTimeFormat::last() {
|
||||
Some(v.next())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(format) = new_format {
|
||||
app.app_time_format = format;
|
||||
}
|
||||
app.footer.set_app_time_format(new_format);
|
||||
}
|
||||
// toogle menu
|
||||
KeyCode::Char('m') => app.footer.set_show_menu(!app.footer.get_show_menu()),
|
||||
KeyCode::Char(',') => {
|
||||
@ -374,7 +405,7 @@ impl App {
|
||||
show_menu: self.footer.get_show_menu(),
|
||||
notification: self.notification,
|
||||
blink: self.blink,
|
||||
app_time_format: *self.footer.app_time_format(),
|
||||
app_time_format: self.app_time_format,
|
||||
style: self.style,
|
||||
with_decis: self.with_decis,
|
||||
pomodoro_mode: self.pomodoro.get_mode().clone(),
|
||||
@ -393,6 +424,7 @@ impl App {
|
||||
),
|
||||
elapsed_value_countdown: Duration::from(*self.countdown.get_elapsed_value()),
|
||||
current_value_timer: Duration::from(*self.timer.get_clock().get_current_value()),
|
||||
footer_app_time: self.footer.app_time_format().is_some().into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
use clap::ValueEnum;
|
||||
use ratatui::symbols::shade;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum::EnumString;
|
||||
use time::OffsetDateTime;
|
||||
use time::format_description;
|
||||
|
||||
@ -15,6 +16,8 @@ pub enum Content {
|
||||
Timer,
|
||||
#[value(name = "pomodoro", alias = "p")]
|
||||
Pomodoro,
|
||||
// #[value(name = "localclock", alias = "l")]
|
||||
// LocalClock,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@ -71,7 +74,7 @@ impl Style {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, EnumString, Serialize, Deserialize)]
|
||||
pub enum AppTimeFormat {
|
||||
/// `hh:mm:ss`
|
||||
#[default]
|
||||
@ -80,17 +83,22 @@ pub enum AppTimeFormat {
|
||||
HhMm,
|
||||
/// `hh:mm AM` (or PM)
|
||||
Hh12Mm,
|
||||
/// `` (empty)
|
||||
Hidden,
|
||||
}
|
||||
|
||||
impl AppTimeFormat {
|
||||
pub const fn first() -> Self {
|
||||
Self::HhMmSs
|
||||
}
|
||||
|
||||
pub const fn last() -> Self {
|
||||
Self::Hh12Mm
|
||||
}
|
||||
|
||||
pub fn next(&self) -> Self {
|
||||
match self {
|
||||
AppTimeFormat::HhMmSs => AppTimeFormat::HhMm,
|
||||
AppTimeFormat::HhMm => AppTimeFormat::Hh12Mm,
|
||||
AppTimeFormat::Hh12Mm => AppTimeFormat::Hidden,
|
||||
AppTimeFormat::Hidden => AppTimeFormat::HhMmSs,
|
||||
AppTimeFormat::Hh12Mm => AppTimeFormat::HhMmSs,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -113,24 +121,19 @@ impl From<AppTime> for OffsetDateTime {
|
||||
impl AppTime {
|
||||
pub fn format(&self, app_format: &AppTimeFormat) -> String {
|
||||
let parse_str = match app_format {
|
||||
AppTimeFormat::HhMmSs => Some("[hour]:[minute]:[second]"),
|
||||
AppTimeFormat::HhMm => Some("[hour]:[minute]"),
|
||||
AppTimeFormat::Hh12Mm => Some("[hour repr:12 padding:none]:[minute] [period]"),
|
||||
AppTimeFormat::Hidden => None,
|
||||
AppTimeFormat::HhMmSs => "[hour]:[minute]:[second]",
|
||||
AppTimeFormat::HhMm => "[hour]:[minute]",
|
||||
AppTimeFormat::Hh12Mm => "[hour repr:12 padding:none]:[minute] [period]",
|
||||
};
|
||||
|
||||
if let Some(str) = parse_str {
|
||||
format_description::parse(str)
|
||||
.map_err(|_| "parse error")
|
||||
.and_then(|fd| {
|
||||
OffsetDateTime::from(*self)
|
||||
.format(&fd)
|
||||
.map_err(|_| "format error")
|
||||
})
|
||||
.unwrap_or_else(|e| e.to_string())
|
||||
} else {
|
||||
"".to_owned()
|
||||
}
|
||||
format_description::parse(parse_str)
|
||||
.map_err(|_| "parse error")
|
||||
.and_then(|fd| {
|
||||
OffsetDateTime::from(*self)
|
||||
.format(&fd)
|
||||
.map_err(|_| "format error")
|
||||
})
|
||||
.unwrap_or_else(|e| e.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
@ -150,6 +153,15 @@ pub enum Toggle {
|
||||
Off,
|
||||
}
|
||||
|
||||
impl From<bool> for Toggle {
|
||||
fn from(value: bool) -> Self {
|
||||
match value {
|
||||
true => Toggle::On,
|
||||
false => Toggle::Off,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
@ -196,12 +208,5 @@ mod tests {
|
||||
"6:06 PM",
|
||||
"local"
|
||||
);
|
||||
// hidden
|
||||
assert_eq!(AppTime::Utc(dt).format(&AppTimeFormat::Hidden), "", "utc");
|
||||
assert_eq!(
|
||||
AppTime::Local(dt).format(&AppTimeFormat::Hidden),
|
||||
"",
|
||||
"local"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,17 +3,30 @@ use crate::{
|
||||
widgets::pomodoro::Mode as PomodoroMode,
|
||||
};
|
||||
use color_eyre::eyre::Result;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
|
||||
fn deserialize_app_time_format<'de, D>(deserializer: D) -> Result<AppTimeFormat, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let s = String::deserialize(deserializer)?;
|
||||
match s.as_str() {
|
||||
// Hidden is deprecated - use `default` value instead
|
||||
"Hidden" => Ok(AppTimeFormat::default()),
|
||||
_ => s.parse().map_err(serde::de::Error::custom),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct AppStorage {
|
||||
pub content: Content,
|
||||
pub show_menu: bool,
|
||||
pub notification: Toggle,
|
||||
pub blink: Toggle,
|
||||
#[serde(deserialize_with = "deserialize_app_time_format")]
|
||||
pub app_time_format: AppTimeFormat,
|
||||
pub style: Style,
|
||||
pub with_decis: bool,
|
||||
@ -31,6 +44,8 @@ pub struct AppStorage {
|
||||
pub elapsed_value_countdown: Duration,
|
||||
// timer
|
||||
pub current_value_timer: Duration,
|
||||
// footer
|
||||
pub footer_app_time: Toggle,
|
||||
}
|
||||
|
||||
impl Default for AppStorage {
|
||||
@ -60,6 +75,8 @@ impl Default for AppStorage {
|
||||
elapsed_value_countdown: Duration::ZERO,
|
||||
// timer
|
||||
current_value_timer: Duration::ZERO,
|
||||
// footer
|
||||
footer_app_time: Toggle::Off,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,11 +13,11 @@ use ratatui::{
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FooterState {
|
||||
show_menu: bool,
|
||||
app_time_format: AppTimeFormat,
|
||||
app_time_format: Option<AppTimeFormat>,
|
||||
}
|
||||
|
||||
impl FooterState {
|
||||
pub const fn new(show_menu: bool, app_time_format: AppTimeFormat) -> Self {
|
||||
pub const fn new(show_menu: bool, app_time_format: Option<AppTimeFormat>) -> Self {
|
||||
Self {
|
||||
show_menu,
|
||||
app_time_format,
|
||||
@ -32,12 +32,12 @@ impl FooterState {
|
||||
self.show_menu
|
||||
}
|
||||
|
||||
pub const fn app_time_format(&self) -> &AppTimeFormat {
|
||||
pub const fn app_time_format(&self) -> &Option<AppTimeFormat> {
|
||||
&self.app_time_format
|
||||
}
|
||||
|
||||
pub fn toggle_app_time_format(&mut self) {
|
||||
self.app_time_format = self.app_time_format.next();
|
||||
pub const fn set_app_time_format(&mut self, value: Option<AppTimeFormat>) {
|
||||
self.app_time_format = value;
|
||||
}
|
||||
}
|
||||
|
||||
@ -73,9 +73,9 @@ impl StatefulWidget for Footer {
|
||||
Line::from(
|
||||
match state.app_time_format {
|
||||
// `Hidden` -> no (empty) title
|
||||
AppTimeFormat::Hidden => "".into(),
|
||||
None => "".into(),
|
||||
// others -> add some space around
|
||||
_ => format!(" {} ", self.app_time.format(&state.app_time_format))
|
||||
Some(v) => format!(" {} ", self.app_time.format(&v))
|
||||
}
|
||||
).right_aligned())
|
||||
.border_set(border::PLAIN)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user