Add pomodoro, microbreaks, breathing guide, screen dimming, presentation mode, goals, multi-monitor, and activity manager
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user