Add pomodoro, microbreaks, breathing guide, screen dimming, presentation mode, goals, multi-monitor, and activity manager

This commit is contained in:
2026-02-07 15:11:44 +02:00
parent 3ca3857141
commit be41f61740
28 changed files with 3792 additions and 448 deletions

View File

@@ -42,8 +42,31 @@ pub struct StatsSnapshot {
pub compliance_rate: f64,
pub current_streak: u32,
pub best_streak: u32,
// F10: Daily goal
pub daily_goal_progress: u32,
pub daily_goal_met: bool,
}
/// F10: Result of recording a completed break
pub struct BreakCompletedResult {
pub milestone_reached: Option<u32>,
pub daily_goal_just_met: bool,
}
/// F7: Weekly summary for reports
#[derive(Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct WeekSummary {
pub week_start: String,
pub total_completed: u32,
pub total_skipped: u32,
pub total_break_time_secs: u64,
pub compliance_rate: f64,
pub avg_daily_completed: f64,
}
const MILESTONES: &[u32] = &[3, 5, 7, 14, 21, 30, 50, 100, 365];
impl Stats {
/// Portable: stats file lives next to the exe
fn stats_path() -> Option<PathBuf> {
@@ -91,12 +114,23 @@ impl Stats {
})
}
pub fn record_break_completed(&mut self, duration_secs: u64) {
/// Record a completed break. Returns milestone/goal info for gamification.
pub fn record_break_completed(&mut self, duration_secs: u64, daily_goal: u32) -> BreakCompletedResult {
let day = self.today_mut();
let was_below_goal = day.breaks_completed < daily_goal;
day.breaks_completed += 1;
day.total_break_time_secs += duration_secs;
let now_at_goal = day.breaks_completed >= daily_goal;
self.update_streak();
self.save();
let milestone = self.check_milestone();
let daily_goal_just_met = was_below_goal && now_at_goal && daily_goal > 0;
BreakCompletedResult {
milestone_reached: milestone,
daily_goal_just_met,
}
}
pub fn record_break_skipped(&mut self) {
@@ -148,7 +182,17 @@ impl Stats {
}
}
pub fn snapshot(&self) -> StatsSnapshot {
/// F10: Check if current streak exactly matches a milestone
fn check_milestone(&self) -> Option<u32> {
let streak = self.data.current_streak;
if MILESTONES.contains(&streak) {
Some(streak)
} else {
None
}
}
pub fn snapshot(&self, daily_goal: u32) -> StatsSnapshot {
let key = Self::today_key();
let today = self.data.days.get(&key);
@@ -176,6 +220,8 @@ impl Stats {
compliance_rate: compliance,
current_streak: self.data.current_streak,
best_streak: self.data.best_streak,
daily_goal_progress: completed,
daily_goal_met: daily_goal > 0 && completed >= daily_goal,
}
}
@@ -196,4 +242,47 @@ impl Stats {
records
}
/// F7: Get weekly summaries for the past N weeks
pub fn weekly_summary(&self, weeks: u32) -> Vec<WeekSummary> {
let today = chrono::Local::now().date_naive();
let mut summaries = Vec::new();
for w in 0..weeks {
let week_end = today - chrono::Duration::days((w * 7) as i64);
let week_start = week_end - chrono::Duration::days(6);
let mut total_completed = 0u32;
let mut total_skipped = 0u32;
let mut total_break_time = 0u64;
for d in 0..7 {
let day = week_start + chrono::Duration::days(d);
let key = day.format("%Y-%m-%d").to_string();
if let Some(record) = self.data.days.get(&key) {
total_completed += record.breaks_completed;
total_skipped += record.breaks_skipped;
total_break_time += record.total_break_time_secs;
}
}
let total = total_completed + total_skipped;
let compliance = if total > 0 {
total_completed as f64 / total as f64
} else {
1.0
};
summaries.push(WeekSummary {
week_start: week_start.format("%Y-%m-%d").to_string(),
total_completed,
total_skipped,
total_break_time_secs: total_break_time,
compliance_rate: compliance,
avg_daily_completed: total_completed as f64 / 7.0,
});
}
summaries
}
}