feat: add multiple productivity features and UI enhancements #68

Merged
naomi merged 21 commits from feat/stuffy-stuff into main 2026-01-25 22:19:01 -08:00
2 changed files with 1557 additions and 2 deletions
Showing only changes of commit 4f02747064 - Show all commits
File diff suppressed because it is too large Load Diff
+72
View File
@@ -1,4 +1,5 @@
use crate::achievements::{check_achievements, AchievementProgress};
use chrono::{Local, Timelike};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::time::Instant;
@@ -28,6 +29,14 @@ pub struct UsageStats {
#[serde(skip)]
pub session_start: Option<Instant>,
// Extended tracking for achievements
pub sessions_started: u64,
pub consecutive_days: u64,
pub total_days_used: u64,
pub morning_sessions: u64, // Sessions started before 9 AM
pub night_sessions: u64, // Sessions started after 10 PM
pub last_session_date: Option<String>, // ISO date string for streak tracking
// Achievement tracking
#[serde(skip)]
pub achievements: AchievementProgress,
@@ -65,6 +74,47 @@ impl UsageStats {
self.session_duration_seconds = 0;
self.session_start = Some(Instant::now());
self.achievements.start_session();
// Track session start for achievements
self.track_session_start();
}
pub fn track_session_start(&mut self) {
let now = Local::now();
let today = now.format("%Y-%m-%d").to_string();
let hour = now.hour();
// Increment session count
self.sessions_started += 1;
// Track morning/night sessions
if hour < 9 {
self.morning_sessions += 1;
}
if hour >= 22 {
self.night_sessions += 1;
}
// Track consecutive days and total days
if let Some(last_date) = &self.last_session_date {
if last_date != &today {
// Check if it's the next day (consecutive)
if is_consecutive_day(last_date, &today) {
self.consecutive_days += 1;
} else {
// Streak broken
self.consecutive_days = 1;
}
self.total_days_used += 1;
self.last_session_date = Some(today);
}
// Same day - don't increment anything
} else {
// First session ever
self.consecutive_days = 1;
self.total_days_used = 1;
self.last_session_date = Some(today);
}
}
pub fn increment_messages(&mut self) {
@@ -127,12 +177,34 @@ impl UsageStats {
session_tools_usage: self.session_tools_usage.clone(),
session_duration_seconds: self.session_duration_seconds,
session_start: self.session_start,
sessions_started: self.sessions_started,
consecutive_days: self.consecutive_days,
total_days_used: self.total_days_used,
morning_sessions: self.morning_sessions,
night_sessions: self.night_sessions,
last_session_date: self.last_session_date.clone(),
achievements: AchievementProgress::new(), // Dummy for copy
};
check_achievements(&stats_copy, &mut self.achievements)
}
}
// Helper function to check if two dates are consecutive
fn is_consecutive_day(prev_date: &str, current_date: &str) -> bool {
use chrono::NaiveDate;
let prev = NaiveDate::parse_from_str(prev_date, "%Y-%m-%d").ok();
let current = NaiveDate::parse_from_str(current_date, "%Y-%m-%d").ok();
match (prev, current) {
(Some(p), Some(c)) => {
let diff = c.signed_duration_since(p).num_days();
diff == 1
}
_ => false,
}
}
// Pricing as of January 2025
// https://www.anthropic.com/pricing
fn calculate_cost(input_tokens: u64, output_tokens: u64, model: &str) -> f64 {