use stint_core::models::entry::{EntryFilter, TimeEntry};
use stint_core::models::project::Project;
use stint_core::service::StintService;
use stint_core::storage::sqlite::SqliteStorage;
use time::OffsetDateTime;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Panel {
Today,
Timeline,
Week,
}
impl Panel {
pub fn next(self) -> Self {
match self {
Self::Today => Self::Timeline,
Self::Timeline => Self::Week,
Self::Week => Self::Today,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TimelineView {
Today,
Yesterday,
}
impl TimelineView {
pub fn next(self) -> Self {
match self {
Self::Today => Self::Yesterday,
Self::Yesterday => Self::Today,
}
}
}
pub struct App {
service: StintService<SqliteStorage>,
pub running_timer: Option<(TimeEntry, Project)>,
pub today_entries: Vec<(TimeEntry, Project)>,
pub yesterday_entries: Vec<(TimeEntry, Project)>,
pub week_totals: Vec<(String, i64)>,
pub selected_panel: Panel,
pub today_scroll: usize,
pub timeline_scroll: usize,
pub week_scroll: usize,
pub timeline_view: TimelineView,
pub should_quit: bool,
}
impl App {
pub fn new(storage: SqliteStorage) -> Self {
let service = StintService::new(storage);
let mut app = Self {
service,
running_timer: None,
today_entries: vec![],
yesterday_entries: vec![],
week_totals: vec![],
selected_panel: Panel::Today,
today_scroll: 0,
timeline_scroll: 0,
week_scroll: 0,
timeline_view: TimelineView::Today,
should_quit: false,
};
app.refresh();
app
}
pub fn refresh(&mut self) {
let now = OffsetDateTime::now_local().unwrap_or_else(|_| OffsetDateTime::now_utc());
self.running_timer = self.service.get_status().unwrap_or(None);
let today_start = now.replace_time(time::Time::MIDNIGHT);
let today_filter = EntryFilter {
from: Some(today_start),
..Default::default()
};
self.today_entries = self.service.get_entries(&today_filter).unwrap_or_default();
let yesterday_start = today_start - time::Duration::days(1);
let yesterday_filter = EntryFilter {
from: Some(yesterday_start),
to: Some(today_start),
..Default::default()
};
self.yesterday_entries = self
.service
.get_entries(&yesterday_filter)
.unwrap_or_default();
let weekday = now.weekday().number_days_from_monday();
let week_start = today_start - time::Duration::days(weekday as i64);
let week_filter = EntryFilter {
from: Some(week_start),
..Default::default()
};
let week_entries = self.service.get_entries(&week_filter).unwrap_or_default();
let mut totals: std::collections::BTreeMap<String, i64> = std::collections::BTreeMap::new();
for (entry, project) in &week_entries {
let duration = entry.computed_duration_secs().unwrap_or(0);
*totals.entry(project.name.clone()).or_insert(0) += duration;
}
self.week_totals = totals.into_iter().collect();
self.week_totals
.sort_by_key(|(_, total)| std::cmp::Reverse(*total));
}
pub fn scroll_up(&mut self) {
match self.selected_panel {
Panel::Today => {
self.today_scroll = self.today_scroll.saturating_sub(1);
}
Panel::Timeline => {
self.timeline_scroll = self.timeline_scroll.saturating_sub(1);
}
Panel::Week => {
self.week_scroll = self.week_scroll.saturating_sub(1);
}
}
}
pub fn scroll_down(&mut self) {
match self.selected_panel {
Panel::Today => {
let max = self.today_entries.len().saturating_sub(1);
if self.today_scroll < max {
self.today_scroll += 1;
}
}
Panel::Timeline => {
let max = self.timeline_scroll_items().saturating_sub(1);
if self.timeline_scroll < max {
self.timeline_scroll += 1;
}
}
Panel::Week => {
let max = self.week_totals.len().saturating_sub(1);
if self.week_scroll < max {
self.week_scroll += 1;
}
}
}
}
pub fn timeline_entries(&self) -> &[(TimeEntry, Project)] {
match self.timeline_view {
TimelineView::Today => &self.today_entries,
TimelineView::Yesterday => &self.yesterday_entries,
}
}
pub fn timeline_scroll_items(&self) -> usize {
super::timeline::line_count(self.timeline_entries(), self.timeline_view)
}
pub fn toggle_timeline_view(&mut self) {
self.timeline_view = self.timeline_view.next();
self.timeline_scroll = 0;
}
pub fn is_timeline_today(&self) -> bool {
self.timeline_view == TimelineView::Today
}
}