tui_breath 0.4.0

Terminal breathing guide built with Rust + Ratatui. 6 patterns, breath hold, workout mode, smooth animations, JSON session tracking.
use chrono::{DateTime, Utc};

#[derive(Debug, Clone)]
pub struct BreathHoldAttempt {
    pub started_at: DateTime<Utc>,
    pub ended_at: DateTime<Utc>,
    pub duration_secs: f64,
}

#[derive(Debug, Clone)]
pub struct BreathHoldRuntime {
    pub started_at: DateTime<Utc>,
    pub elapsed_secs: f64,
}

impl BreathHoldRuntime {
    pub fn new(started_at: DateTime<Utc>) -> Self {
        Self {
            started_at,
            elapsed_secs: 0.0,
        }
    }

    pub fn tick(&mut self, delta_secs: f64) {
        self.elapsed_secs += delta_secs;
    }

    pub fn finish(self, ended_at: DateTime<Utc>) -> BreathHoldAttempt {
        BreathHoldAttempt {
            started_at: self.started_at,
            ended_at,
            duration_secs: self.elapsed_secs,
        }
    }
}

pub fn best_hold_seconds(attempts: &[BreathHoldAttempt]) -> Option<f64> {
    attempts
        .iter()
        .map(|attempt| attempt.duration_secs)
        .max_by(|left, right| left.partial_cmp(right).unwrap_or(std::cmp::Ordering::Equal))
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn hold_runtime_accumulates_elapsed_time() {
        let mut runtime = BreathHoldRuntime::new(Utc::now());
        runtime.tick(1.5);
        runtime.tick(2.0);

        assert!((runtime.elapsed_secs - 3.5).abs() < f64::EPSILON);
    }

    #[test]
    fn best_hold_uses_longest_attempt() {
        let now = Utc::now();
        let attempts = vec![
            BreathHoldAttempt {
                started_at: now,
                ended_at: now,
                duration_secs: 11.2,
            },
            BreathHoldAttempt {
                started_at: now,
                ended_at: now,
                duration_secs: 17.8,
            },
        ];

        assert_eq!(best_hold_seconds(&attempts), Some(17.8));
    }
}