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